seekia/internal/network/manualBroadcasts/manualBroadcasts.go

1097 lines
39 KiB
Go

// manualBroadcasts provides functions to initiate and monitor manual broadcasts
// Manual broadcasts are tasks which run in the background to broadcast profiles, messages, reviews, reports, and parameters
// These broadcasts are initiated by the user through the GUI, and their status can be monitored by the user
// An example of its use is Mate profiles, will will always be manually broadcasted
// This package provides the ability to get the status of each broadcast
// All broadcasted content will be continually broadcasted in the background, regardless of whether it was broadcasted manually or not
// networkJobs.go provides functions to broadcast content automatically in the background to make sure the content is on the network
package manualBroadcasts
//TODO: Call the myReviews.DeleteMyReviewsListCache() function whenever broadcasting a review, or just remove the ability to use
// this package to broadcast reviews.
import "seekia/internal/byteRange"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/logger"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/moderation/readReports"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/network/eligibleHosts"
import "seekia/internal/network/hostRanges"
import "seekia/internal/network/peerClient"
import "seekia/internal/network/sendRequests"
import "seekia/internal/parameters/readParameters"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/readContent"
import messagepack "github.com/vmihailenco/msgpack/v5"
import "time"
import "sync"
import "errors"
import "slices"
// This object stores data about active processes
type processObject struct{
// Set to true once the process is complete. Will be true if we encounter an error
isComplete bool
// Set to true if we encounter an error
encounteredError bool
// The error we encountered
errorEncountered error
// Stores the number of hosts that have been successfully broadcasted to
numberOfSuccessfulBroadcasts int
// Stores details about process progress, which are shown to user in gui
progressDetails string
}
var processObjectsMapMutex sync.RWMutex
var processObjectsMap map[[22]byte]processObject = make(map[[22]byte]processObject)
//Outputs:
// -[22]byte: New process identifier
// -error
func initializeNewProcessObject()([22]byte, error){
processIdentifierBytes, err := helpers.GetNewRandomBytes(22)
if (err != nil) { return [22]byte{}, err }
processIdentifier := [22]byte(processIdentifierBytes)
newProcessObject := processObject{
isComplete: false,
encounteredError: false,
errorEncountered: nil,
numberOfSuccessfulBroadcasts: 0,
progressDetails: "",
}
processObjectsMapMutex.Lock()
processObjectsMap[processIdentifier] = newProcessObject
processObjectsMapMutex.Unlock()
return processIdentifier, nil
}
func increaseProcessSuccessfulBroadcastsCount(processIdentifier [22]byte)error{
processObjectsMapMutex.Lock()
defer processObjectsMapMutex.Unlock()
processObject, exists := processObjectsMap[processIdentifier]
if (exists == false){
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
return errors.New("increaseProcessSuccessfulBroadcastsCount called with uninitialized process: " + processIdentifierHex)
}
processObject.numberOfSuccessfulBroadcasts += 1
processObjectsMap[processIdentifier] = processObject
return nil
}
func setProcessProgressDetails(processIdentifier [22]byte, newProgressDetails string)error{
processObjectsMapMutex.Lock()
defer processObjectsMapMutex.Unlock()
processObject, exists := processObjectsMap[processIdentifier]
if (exists == false){
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
return errors.New("setProcessProgressDetails called with uninitialized process: " + processIdentifierHex)
}
processObject.progressDetails = newProgressDetails
processObjectsMap[processIdentifier] = processObject
return nil
}
func setProcessEncounteredError(processIdentifier [22]byte, errorEncountered error)error{
processObjectsMapMutex.Lock()
defer processObjectsMapMutex.Unlock()
processObject, exists := processObjectsMap[processIdentifier]
if (exists == false){
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
return errors.New("setProcessEncounteredError called with uninitialized process: " + processIdentifierHex)
}
processObject.isComplete = true
processObject.encounteredError = true
processObject.errorEncountered = errorEncountered
processObjectsMap[processIdentifier] = processObject
return nil
}
func setProcessIsComplete(processIdentifier [22]byte)error{
processObjectsMapMutex.Lock()
defer processObjectsMapMutex.Unlock()
processObject, exists := processObjectsMap[processIdentifier]
if (exists == false){
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
return errors.New("setProcessIsComplete called with uninitialized process: " + processIdentifierHex)
}
processObject.isComplete = true
processObjectsMap[processIdentifier] = processObject
return nil
}
//Outputs:
// -bool: Process found
// -bool: Process is complete status
// -bool: Process encountered error
// -error: Error that process encountered
// -int: Number of hosts successfully broadcasted to
// -string: Process progress details
func GetProcessInfo(processIdentifier [22]byte)(bool, bool, bool, error, int, string){
processObjectsMapMutex.RLock()
defer processObjectsMapMutex.RUnlock()
processObject, exists := processObjectsMap[processIdentifier]
if (exists == false) {
return false, false, false, nil, 0, ""
}
processIsComplete := processObject.isComplete
processEncounteredError := processObject.encounteredError
processError := processObject.errorEncountered
processNumberOfSuccessfulBroadcasts := processObject.numberOfSuccessfulBroadcasts
processProgressDetails := processObject.progressDetails
return true, processIsComplete, processEncounteredError, processError, processNumberOfSuccessfulBroadcasts, processProgressDetails
}
// A broadcast is considered complete when a specified number of hosts have been contacted and have responded with WillHostContent=Yes
//
// Non-Parameters content provided to this function must be of the same profileIdentityHash/messageInbox
// This is because they must be acceptable by the same hosts
//
// Multiple reviews/reports are not allowed. We will only need to broadcast 1 review/report at a time.
//
//Outputs:
// -bool: At least 1 host found to contact
// -[22]byte: Process Identifier
// -error
func StartContentBroadcast(contentType string, contentNetworkType byte, contentList [][]byte, numberOfHostsToContact int)(bool, [22]byte, error){
if (len(contentList) == 0){
return false, [22]byte{}, errors.New("StartContentBroadcast called with empty contentList")
}
if (contentType == "Review" || contentType == "Report"){
if (len(contentList) != 1){
return false, [22]byte{}, errors.New("StartContentBroadcast does not accept more than 1 review or report")
}
}
isValid := helpers.VerifyNetworkType(contentNetworkType)
if (isValid == false){
contentNetworkTypeString := helpers.ConvertByteToString(contentNetworkType)
return false, [22]byte{}, errors.New("StartContentBroadcast called with invalid contentNetworkType: " + contentNetworkTypeString)
}
hostsToContactList, err := GetAvailableHostsToAcceptBroadcastList(contentType, contentNetworkType, contentList)
if (err != nil) { return false, [22]byte{}, err }
if (len(hostsToContactList) == 0){
// No eligible hosts to contact exist. Broadcast is impossible.
// User should wait for their client to download more hosts and then try again.
return false, [22]byte{}, nil
}
/// Now we get content hashes.
contentHashesList := make([][]byte, 0, len(contentList))
for _, contentBytes := range contentList{
ableToRead, contentHash, err := readContent.GetContentHashFromContentBytes(true, contentType, contentBytes)
if (err != nil) { return false, [22]byte{}, err }
if (ableToRead == false){
return false, [22]byte{}, errors.New("StartContentBroadcast called with invalid " + contentType + " content to broadcast.")
}
contentHashesList = append(contentHashesList, contentHash)
}
processIdentifier, err := initializeNewProcessObject()
if (err != nil) { return false, [22]byte{}, err }
performBroadcasts := func(){
var contactedHostsListMutex sync.RWMutex
// We use this list to prevent broadcasting to the same host twice
contactedHostIdentityHashesList := make([][16]byte, 0)
checkIfHostHasBeenContacted := func(hostIdentityHash [16]byte)bool{
contactedHostsListMutex.RLock()
isContacted := slices.Contains(contactedHostIdentityHashesList, hostIdentityHash)
contactedHostsListMutex.RUnlock()
return isContacted
}
addContactedHostIdentityHashToList := func(contactedHostIdentityHash [16]byte){
contactedHostsListMutex.Lock()
contactedHostIdentityHashesList = append(contactedHostIdentityHashesList, contactedHostIdentityHash)
contactedHostsListMutex.Unlock()
}
var activeBroadcastsMutex sync.RWMutex
numberOfActiveBroadcasts := 0
increaseActiveBroadcasts := func(){
activeBroadcastsMutex.Lock()
numberOfActiveBroadcasts += 1
activeBroadcastsMutex.Unlock()
}
decreaseActiveBroadcasts := func(){
activeBroadcastsMutex.Lock()
numberOfActiveBroadcasts -= 1
activeBroadcastsMutex.Unlock()
}
getNumberOfActiveBroadcasts := func()int{
activeBroadcastsMutex.RLock()
result := numberOfActiveBroadcasts
activeBroadcastsMutex.RUnlock()
return result
}
executeBroadcastToHost := func(hostIdentityHash [16]byte){
executeBroadcastToHostFunction := func()error{
processFound, processIsComplete, errorEncountered, _, _, _ := GetProcessInfo(processIdentifier)
if (processFound == false){
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
return errors.New("executeBroadcastToHostFunction called with uninitialized process: " + processIdentifierHex)
}
if (processIsComplete == true || errorEncountered == true){
// Error may have been encountered from a different broadcast goroutine
return nil
}
hostIdentityHashString, identityType, err := identity.EncodeIdentityHashBytesToString(hostIdentityHash)
if (err != nil) { return err }
if (identityType != "Moderator"){
return errors.New("executeBroadcastToHost called with non-moderator identity hash.")
}
hostIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(hostIdentityHashString, 6)
if (err != nil) { return err }
hostProfileFound, connectionEstablished, connectionIdentifier, err := peerClient.EstablishNewConnectionToHost(false, hostIdentityHash, contentNetworkType)
if (err != nil) { return err }
if (hostProfileFound == false){
return nil
}
if (connectionEstablished == false){
err := setProcessProgressDetails(processIdentifier, "Failed to connect to host " + hostIdentityHashTrimmed)
if (err != nil) { return err }
return nil
}
err = setProcessProgressDetails(processIdentifier, "Broadcasting to host " + hostIdentityHashTrimmed)
if (err != nil) { return err }
processFound, processIsComplete, errorEncountered, _, _, _ = GetProcessInfo(processIdentifier)
if (processFound == false){
// This should not happen
return errors.New("Process not found after being found already.")
}
if (processIsComplete == true || errorEncountered == true){
// Error may have been encountered from a different broadcast goroutine
return nil
}
successfulDownload, contentAcceptedInfoMap, err := sendRequests.BroadcastContentToHost(connectionIdentifier, hostIdentityHash, contentNetworkType, contentType, contentList)
if (err != nil) { return err }
if (successfulDownload == false){
err := setProcessProgressDetails(processIdentifier, "Failed to broadcast to host " + hostIdentityHashTrimmed)
if (err != nil) { return err }
return nil
}
getAllContentAcceptedStatus := func()(bool, error){
for _, contentHash := range contentHashesList{
contentAcceptedStatus, exists := contentAcceptedInfoMap[string(contentHash)]
if (exists == false){
return false, errors.New("BroadcastContentToHost not verifying contentAcceptedInfoMap contains content hash")
}
if (contentAcceptedStatus == false){
return false, nil
}
}
return true, nil
}
contentAcceptedStatus, err := getAllContentAcceptedStatus()
if (err != nil) { return err }
if (contentAcceptedStatus == true){
err := increaseProcessSuccessfulBroadcastsCount(processIdentifier)
if (err != nil) { return err }
err = setProcessProgressDetails(processIdentifier, "Successful broadcast to host " + hostIdentityHashTrimmed)
if (err != nil) { return err }
}
return nil
}
err := executeBroadcastToHostFunction()
if (err != nil){
logger.AddLogError("General", err)
err := setProcessEncounteredError(processIdentifier, err)
if (err != nil){
logger.AddLogError("General", err)
}
}
decreaseActiveBroadcasts()
}
startTime := time.Now().Unix()
for {
processFound, processIsComplete, errorEncountered, _, numberOfSuccessfulBroadcasts, _ := GetProcessInfo(processIdentifier)
if (processFound == false){
// This should not happen
logger.AddLogError("General", errors.New("Process not found during manualBroadcasts loop 1."))
return
}
if (processIsComplete == true || errorEncountered == true){
return
}
if (numberOfSuccessfulBroadcasts >= numberOfHostsToContact){
// We have completed the required number of broadcasts
// Nothing left to do
break
}
activeBroadcasts := getNumberOfActiveBroadcasts()
pendingAndCompletedBroadcasts := numberOfSuccessfulBroadcasts + activeBroadcasts
if (pendingAndCompletedBroadcasts >= numberOfHostsToContact){
// We are actively broadcasting to the required number of hosts
// We wait to see if we need to retry to another host
currentTime := time.Now().Unix()
secondsElapsed := currentTime - startTime
if (secondsElapsed > 150){
// Something has gone wrong. Broadcasts should timeout before this.
err := setProcessEncounteredError(processIdentifier, errors.New("Broadcast failed: Reached timeout."))
if (err != nil){
logger.AddLogError("General", err)
}
return
}
time.Sleep(time.Second)
continue
}
// We need to start another broadcast
// We find a host we have not contacted yet
//Output:
// -bool: We found a host
startNewBroadcast := func()bool{
for _, hostIdentityHash := range hostsToContactList{
isContacted := checkIfHostHasBeenContacted(hostIdentityHash)
if (isContacted == false){
addContactedHostIdentityHashToList(hostIdentityHash)
increaseActiveBroadcasts()
go executeBroadcastToHost(hostIdentityHash)
return true
}
}
return false
}
foundHost := startNewBroadcast()
if (foundHost == false){
// There are not enough hosts to contact the requested numberOfHostsToContact
break
}
}
// We wait for broadcasts to complete
secondsElapsed := 0
for {
processFound, processIsComplete, errorEncountered, _, _, _ := GetProcessInfo(processIdentifier)
if (processFound == false){
// This should not happen
logger.AddLogError("General", errors.New("Process not found while waiting for manualBroadcasts to complete."))
return
}
if (processIsComplete == true || errorEncountered == true){
// Error may have been encountered from a different broadcast goroutine
return
}
activeBroadcasts := getNumberOfActiveBroadcasts()
if (activeBroadcasts <= 0){
// We are done waiting for broadcasts to complete
break
}
time.Sleep(time.Second)
secondsElapsed += 1
if (secondsElapsed > 100){
// Something has gone wrong.
err := setProcessEncounteredError(processIdentifier, errors.New("Broadcast failed: Reached timeout."))
if (err != nil){
logger.AddLogError("General", err)
}
return
}
}
err := setProcessIsComplete(processIdentifier)
if (err != nil){
logger.AddLogError("General", err)
return
}
processFound, processIsComplete, errorEncountered, _, numberOfSuccessfulBroadcasts, _ := GetProcessInfo(processIdentifier)
if (processFound == false){
// This should not happen
return
}
if (processIsComplete == false){
// This should not happen
err := setProcessEncounteredError(processIdentifier, errors.New("setProcessIsComplete is not working."))
if (err != nil) {
logger.AddLogError("General", err)
}
return
}
if (errorEncountered == true){
return
}
getFinalBroadcastStatus := func()string{
if (numberOfSuccessfulBroadcasts != 0){
return "Broadcast complete!"
}
return "Broadcast failed: Hosts unavailable."
}
finalBroadcastStatus := getFinalBroadcastStatus()
err = setProcessProgressDetails(processIdentifier, finalBroadcastStatus)
if (err != nil){
logger.AddLogError("General", err)
return
}
}
go performBroadcasts()
return true, processIdentifier, nil
}
// This function will return a list of all hosts who are available to accept our content, and will host our content
// Non-Parameters content provided to this function must be of the same profileIdentityHash/messageInbox
// This is because they must be acceptable by the same hosts
// Multiple contents are not allowed for reviews and reports
func GetAvailableHostsToAcceptBroadcastList(contentType string, contentNetworkType byte, contentList [][]byte)([][16]byte, error){
if (contentType != "Parameters" && contentType != "Profile" && contentType != "Message" && contentType != "Review" && contentType != "Report"){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid contentType: " + contentType)
}
isValid := helpers.VerifyNetworkType(contentNetworkType)
if (isValid == false){
contentNetworkTypeString := helpers.ConvertByteToString(contentNetworkType)
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid contentNetworkType: " + contentNetworkTypeString)
}
if (len(contentList) == 0){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with empty contentList")
}
if (contentType == "Review" || contentType == "Report"){
if (len(contentList) != 1){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList does not accept more than 1 review or report")
}
}
// We use this function to determine if host will host the content we are broadcasting
//Outputs:
// -func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error): Host is hosting my content range
// -error
getCheckIfHostWillHostContentFunction := func()(func(map[int]messagepack.RawMessage)(bool, error), error){
if (contentType == "Parameters"){
for _, parametersBytes := range contentList{
ableToRead, _, parametersNetworkType, _, _, _, _, err := readParameters.ReadParameters(true, parametersBytes)
if (err != nil) { return nil, err }
if (ableToRead == false){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid parameters.")
}
if (parametersNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with parameters of different networkType.")
}
}
checkIfHostWillHostParametersFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
exists, isHostingParameters, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(hostRawProfileMap, "HostingParameters")
if (err != nil) { return false, err }
if (exists == false) {
return false, errors.New("Database corrupt: Contains host profile missing HostingParameters")
}
if (isHostingParameters != "Yes"){
return false, nil
}
return true, nil
}
return checkIfHostWillHostParametersFunction, nil
}
if (contentType == "Profile"){
var profilesAuthor [16]byte
for index, profileBytes := range contentList{
ableToRead, _, profileNetworkType, profileAuthor, _, _, _, err := readProfiles.ReadProfile(true, profileBytes)
if (err != nil) { return nil, err }
if (ableToRead == false){
return nil, errors.New("Trying to broadcast invalid profile.")
}
if (profileNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with profile of different networkType.")
}
if (index == 0){
profilesAuthor = profileAuthor
} else {
if (profilesAuthor != profileAuthor){
return nil, errors.New("Trying to broadcast profiles with different profile authors")
}
}
}
identityType, err := identity.GetIdentityTypeFromIdentityHash(profilesAuthor)
if (err != nil) { return nil, err }
checkIfHostWillHostProfileFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
//TODO: Make sure host is hosting unviewable profiles
hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, identityType)
if (err != nil) { return false, err }
if (hostIsHostingIdentityType == false){
return false, nil
}
if (identityType != "Mate"){
// Hosts must host either all or none of Host/Moderator profiles
return true, nil
}
identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, profilesAuthor)
if (err != nil) { return false, err }
if (identityIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
return checkIfHostWillHostProfileFunction, nil
}
if (contentType == "Message"){
var messagesInbox [10]byte
for index, messageBytes := range contentList{
ableToRead, _, messageNetworkType, messageInbox, _, _, _, _, _, _, err := readMessages.ReadChatMessagePublicData(true, messageBytes)
if (err != nil){ return nil, err }
if (ableToRead == false){
return nil, errors.New("Trying to broadcast invalid message.")
}
if (messageNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with message of different networkType.")
}
if (index == 0){
messagesInbox = messageInbox
} else {
if (messagesInbox != messageInbox){
return nil, errors.New("Trying to broadcast messages with different inbox")
}
}
}
checkIfHostWillHostMessageFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
if (err != nil) { return false, err }
if (hostIsHostingMessages == false){
return false, nil
}
inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, messagesInbox)
if (err != nil) { return false, err }
if (inboxIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
return checkIfHostWillHostMessageFunction, nil
}
if (contentType == "Review"){
reviewBytes := contentList[0]
ableToRead, _, reviewNetworkType, _, _, reviewType, reviewedHash, _, _, err := readReviews.ReadReview(true, reviewBytes)
if (err != nil) { return nil, err }
if (ableToRead == false){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid review")
}
if (reviewNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with review of different networkType.")
}
if (reviewType == "Message"){
if (len(reviewedHash) != 26){
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return nil, errors.New("ReadReview returning invalid message reviewedHash: " + reviewedHashHex)
}
reviewedMessageHash := [26]byte(reviewedHash)
messageMetadataExists, _, messageNetworkType, _, reviewedMessageInbox, _, err := contentMetadata.GetMessageMetadata(reviewedMessageHash)
if (err != nil) { return nil, err }
if (messageMetadataExists == true && messageNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with review which reviews a message on a different networkType.")
}
checkIfHostWillHostReviewFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
if (err != nil){ return false, err }
if (hostIsHostingMessages == false){
return false, nil
}
if (messageMetadataExists == true){
inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, reviewedMessageInbox)
if (err != nil) { return false, err }
if (inboxIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
// messageMetadataExists == false
// We don't know what inbox the message was sent to
// This should not happen because we presumably just reviewed this message
// We will only contact hosts who are hosting all inboxes
minimumInboxBound, maximumInboxBound := byteRange.GetMinimumMaximumInboxBounds()
if (theirInboxRangeStart == minimumInboxBound && theirInboxRangeEnd == maximumInboxBound){
return true, nil
}
return false, nil
}
return checkIfHostWillHostReviewFunction, nil
}
// reviewType = "Identity" or "Profile" or "Attribute"
//Outputs:
// -string: Identity type
// -bool: Identity hash is known
// -[16]byte: Identity hash
// -error
getReviewedIdentityHash := func()(string, bool, [16]byte, error){
if (reviewType == "Identity"){
if (len(reviewedHash) != 16){
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", false, [16]byte{}, errors.New("ReadReview returning invalid Identity reviewedHash: " + reviewedHashHex)
}
reviewedIdentityHash := [16]byte(reviewedHash)
reviewedIdentityType, err := identity.GetIdentityTypeFromIdentityHash(reviewedIdentityHash)
if (err != nil) { return "", false, [16]byte{}, err }
return reviewedIdentityType, true, reviewedIdentityHash, nil
}
if (reviewType == "Profile"){
if (len(reviewedHash) != 28){
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", false, [16]byte{}, errors.New("ReadReview returning invalid Profile reviewedHash: " + reviewedHashHex)
}
reviewedProfileHash := [28]byte(reviewedHash)
reviewedIdentityType, isDisabled, err := readProfiles.ReadProfileHashMetadata(reviewedProfileHash)
if (err != nil) {
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", false, [16]byte{}, errors.New("ReadReview returning invalid profile review reviewedHash: " + reviewedHashHex)
}
if (isDisabled == true){
return "", false, [16]byte{}, errors.New("ReadReview returning disabled profile as reviewedHash")
}
metadataExists, _, profileNetworkType, profileAuthorIdentityHash, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(reviewedProfileHash)
if (err != nil) { return "", false, [16]byte{}, err }
if (metadataExists == false){
return reviewedIdentityType, false, [16]byte{}, nil
}
if (profileNetworkType != contentNetworkType){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing a different network type profile.")
}
if (profileIsDisabled == true){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing disabled profile.")
}
return reviewedIdentityType, true, profileAuthorIdentityHash, nil
}
// reviewType == "Attribute"
if (len(reviewedHash) != 27){
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", false, [16]byte{}, errors.New("ReadReview returning invalid Attribute reviewedHash: " + reviewedHashHex)
}
reviewedAttributeHash := [27]byte(reviewedHash)
authorIdentityType, _, err := readProfiles.ReadAttributeHashMetadata(reviewedAttributeHash)
if (err != nil){
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", false, [16]byte{}, errors.New("ReadReview returning invalid Attribute reviewedHash: " + reviewedHashHex)
}
metadataExists, _, authorIdentityHash, attributeNetworkType, _, err := profileStorage.GetProfileAttributeMetadata(reviewedAttributeHash)
if (err != nil) { return "", false, [16]byte{}, err }
if (metadataExists == false){
return authorIdentityType, false, [16]byte{}, nil
}
if (attributeNetworkType != contentNetworkType){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with review reviewing a different network type attribute.")
}
return authorIdentityType, true, authorIdentityHash, nil
}
reviewedIdentityType, reviewedIdentityHashKnown, reviewedIdentityHash, err := getReviewedIdentityHash()
if (err != nil) { return nil, err }
checkIfHostWillHostReviewFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, reviewedIdentityType)
if (err != nil) { return false, err }
if (hostIsHostingIdentityType == false){
return false, nil
}
if (reviewedIdentityType != "Mate"){
// Hosts must host either all or none of Host/Moderator profiles. They should host this profile.
return true, nil
}
if (reviewedIdentityHashKnown == true){
identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, reviewedIdentityHash)
if (err != nil) { return false, err }
if (identityIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
// reviewedIdentityHashKnown == false
// This should not happen, because we have presumably just reviewed this identity's profile
// We will require the host to be hosting all identities
minimumIdentityBound, maximumIdentityBound := byteRange.GetMinimumMaximumIdentityHashBounds()
if (hostRangeStart == minimumIdentityBound && hostRangeEnd == maximumIdentityBound){
return true, nil
}
return false, nil
}
return checkIfHostWillHostReviewFunction, nil
}
// contentType == "Report"
reportBytes := contentList[0]
ableToRead, _, reportNetworkType, _, reportType, reportedHash, _, err := readReports.ReadReport(true, reportBytes)
if (err != nil) { return nil, err }
if (ableToRead == false){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with invalid report.")
}
if (reportNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with report of different networkType.")
}
if (reportType == "Message"){
if (len(reportedHash) != 26){
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return nil, errors.New("ReadReport returning invalid length Message reportedHash: " + reportedHashHex)
}
reportedMessageHash := [26]byte(reportedHash)
messageMetadataExists, _, reportedMessageNetworkType, _, reportedMessageInbox, _, err := contentMetadata.GetMessageMetadata(reportedMessageHash)
if (err != nil) { return nil, err }
if (messageMetadataExists == true && reportedMessageNetworkType != contentNetworkType){
return nil, errors.New("GetAvailableHostsToAcceptBroadcastList called with report which reports a message from a different networkType.")
}
checkIfHostWillHostReportFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
hostIsHostingMessages, theirInboxRangeStart, theirInboxRangeEnd, err := hostRanges.GetHostedMessageInboxesRangeFromHostRawProfileMap(hostRawProfileMap)
if (err != nil) { return false, err }
if (hostIsHostingMessages == false){
return false, nil
}
if (messageMetadataExists == true){
inboxIsWithinTheirRange, err := byteRange.CheckIfInboxIsWithinRange(theirInboxRangeStart, theirInboxRangeEnd, reportedMessageInbox)
if (err != nil) { return false, err }
if (inboxIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
// messageMetadataExists == false
// This should not happen, because we presumably just reported this message
// We will only contact hosts who are hosting all inboxes
minimumInboxBound, maximumInboxBound := byteRange.GetMinimumMaximumInboxBounds()
if (theirInboxRangeStart == minimumInboxBound && theirInboxRangeEnd == maximumInboxBound){
return true, nil
}
return false, nil
}
return checkIfHostWillHostReportFunction, nil
}
// reportType = "Identity" or "Profile" or "Attribute"
//Outputs:
// -string: Identity type
// -bool: Identity hash is known
// -[16]byte: Identity hash
// -error
getReportedIdentityHash := func()(string, bool, [16]byte, error){
if (reportType == "Identity"){
if (len(reportedHash) != 16){
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Identity reportedHash: " + reportedHashHex)
}
reportedIdentityHash := [16]byte(reportedHash)
reportedIdentityType, err := identity.GetIdentityTypeFromIdentityHash(reportedIdentityHash)
if (err != nil) { return "", false, [16]byte{}, err }
return reportedIdentityType, true, reportedIdentityHash, nil
}
if (reportType == "Profile"){
if (len(reportedHash) != 28){
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Identity reportedHash: " + reportedHashHex)
}
reportedProfileHash := [28]byte(reportedHash)
reportedIdentityType, profileIsDisabled, err := readProfiles.ReadProfileHashMetadata(reportedProfileHash)
if (err != nil) {
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return "", false, [16]byte{}, errors.New("ReadReport returning invalid profile report reportedHash: " + reportedHashHex)
}
if (profileIsDisabled == true){
return "", false, [16]byte{}, errors.New("ReadReport returning invalid reportedHash: Profile is disabled.")
}
metadataExists, _, profileNetworkType, profileAuthorIdentityHash, _, profileIsDisabled, _, _, err := contentMetadata.GetProfileMetadata(reportedProfileHash)
if (err != nil) { return "", false, [16]byte{}, err }
if (metadataExists == false){
return reportedIdentityType, false, [16]byte{}, nil
}
if (profileNetworkType != contentNetworkType){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting a profile from a different networkType.")
}
if (profileIsDisabled == true){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting disabled profile.")
}
return reportedIdentityType, true, profileAuthorIdentityHash, nil
}
// reportType == "Attribute"
if (len(reportedHash) != 27){
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return "", false, [16]byte{}, errors.New("ReadReport returning invalid length Attribute reportedHash: " + reportedHashHex)
}
reportedAttributeHash := [27]byte(reportedHash)
authorIdentityType, _, err := readProfiles.ReadAttributeHashMetadata(reportedAttributeHash)
if (err != nil) {
reportedHashHex := encoding.EncodeBytesToHexString(reportedHash)
return "", false, [16]byte{}, errors.New("ReadReport returning invalid Attribute reportedHash: " + reportedHashHex)
}
metadataExists, _, authorIdentityHash, attributeNetworkType, _, err := profileStorage.GetProfileAttributeMetadata(reportedAttributeHash)
if (err != nil) { return "", false, [16]byte{}, err }
if (metadataExists == false){
return authorIdentityType, false, [16]byte{}, nil
}
if (attributeNetworkType != contentNetworkType){
return "", false, [16]byte{}, errors.New("GetAvailableHostsToAcceptBroadcastList called with report reporting an attribute from a different networkType.")
}
return authorIdentityType, true, authorIdentityHash, nil
}
reportedIdentityType, reportedIdentityHashKnown, reportedIdentityHash, err := getReportedIdentityHash()
if (err != nil) { return nil, err }
checkIfHostWillHostReportFunction := func(hostRawProfileMap map[int]messagepack.RawMessage)(bool, error){
hostIsHostingIdentityType, hostRangeStart, hostRangeEnd, err := hostRanges.GetHostedIdentityHashRangeFromHostRawProfileMap(hostRawProfileMap, reportedIdentityType)
if (err != nil) { return false, err }
if (hostIsHostingIdentityType == false){
return false, nil
}
if (reportedIdentityType != "Mate"){
// Hosts must host either all or none of Host/Moderator profiles. They should host this profile.
return true, nil
}
if (reportedIdentityHashKnown == true){
identityIsWithinTheirRange, err := byteRange.CheckIfIdentityHashIsWithinRange(hostRangeStart, hostRangeEnd, reportedIdentityHash)
if (err != nil) { return false, err }
if (identityIsWithinTheirRange == false){
return false, nil
}
return true, nil
}
// reportedIdentityHashKnown == false
// This should not happen, because we presumably just reported the profile
// We will only broadcast to hosts whom are hosting all identities
minimumIdentityBound, maximumIdentityBound := byteRange.GetMinimumMaximumIdentityHashBounds()
if (hostRangeStart == minimumIdentityBound && hostRangeEnd == maximumIdentityBound){
return true, nil
}
return false, nil
}
return checkIfHostWillHostReportFunction, nil
}
checkIfHostWillHostContentFunction, err := getCheckIfHostWillHostContentFunction()
if (err != nil) { return nil, err }
allEligibleHostIdentityHashesList, err := eligibleHosts.GetEligibleHostsList(contentNetworkType)
if (err != nil) { return nil, err }
// We use this list to store hosts who are hosting the content we are broadcasting
availableHostsList := make([][16]byte, 0)
for _, hostIdentityHash := range allEligibleHostIdentityHashesList{
exists, _, _, _, _, hostRawProfileMap, err := profileStorage.GetNewestUserProfile(hostIdentityHash, contentNetworkType)
if (err != nil) { return nil, err }
if (exists == false){
continue
}
isHostingMyContent, err := checkIfHostWillHostContentFunction(hostRawProfileMap)
if (err != nil) { return nil, err }
if (isHostingMyContent == true){
availableHostsList = append(availableHostsList, hostIdentityHash)
}
}
return availableHostsList, nil
}