1097 lines
39 KiB
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
|
|
}
|
|
|
|
|
|
|