// 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 creation 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 creationTimes // We must compare the profile hashes var newestProfileHash [28]byte newestProfileVersion := 0 newestProfileCreationTime := 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, profileCreationTime, _, 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 || profileCreationTime > newestProfileCreationTime){ newestProfileVersion = profileVersion newestProfileHash = profileHash newestProfileCreationTime = profileCreationTime newestProfileRawProfileMap = rawProfileMap newestProfileBytes = profileBytes } anyProfileFound = true } if (anyProfileFound == false){ return false, 0, [28]byte{}, nil, 0, nil, nil } return true, newestProfileVersion, newestProfileHash, newestProfileBytes, newestProfileCreationTime, 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 }