2024-04-11 15:51:56 +02:00
// 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
2024-06-11 06:59:06 +02:00
// -int64: Profile creation time
2024-04-11 15:51:56 +02:00
// -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
2024-06-11 06:59:06 +02:00
//TODO: Deal with 2 profiles with identical creationTimes
2024-04-11 15:51:56 +02:00
// We must compare the profile hashes
var newestProfileHash [ 28 ] byte
newestProfileVersion := 0
2024-06-11 06:59:06 +02:00
newestProfileCreationTime := int64 ( 0 )
2024-04-11 15:51:56 +02:00
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
}
2024-06-11 06:59:06 +02:00
ableToRead , profileVersion , profileNetworkType , profileIdentityHash , profileCreationTime , _ , rawProfileMap , err := readProfiles . ReadProfile ( false , profileBytes )
2024-04-11 15:51:56 +02:00
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
}
2024-06-11 06:59:06 +02:00
if ( anyProfileFound == false || profileCreationTime > newestProfileCreationTime ) {
2024-04-11 15:51:56 +02:00
newestProfileVersion = profileVersion
newestProfileHash = profileHash
2024-06-11 06:59:06 +02:00
newestProfileCreationTime = profileCreationTime
2024-04-11 15:51:56 +02:00
newestProfileRawProfileMap = rawProfileMap
newestProfileBytes = profileBytes
}
anyProfileFound = true
}
if ( anyProfileFound == false ) {
return false , 0 , [ 28 ] byte { } , nil , 0 , nil , nil
}
2024-06-11 06:59:06 +02:00
return true , newestProfileVersion , newestProfileHash , newestProfileBytes , newestProfileCreationTime , newestProfileRawProfileMap , nil
2024-04-11 15:51:56 +02:00
}
// 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
}