// 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 }