diff --git a/Makefile b/Makefile index 2e56e3530bb3ef5d03352961f8eef095b4a47c01..bd6383ff69c75244eaf1b62ef3cc9c0fb91c5154 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,8 @@ build: update_release: GOFLAGS="" go get -u gitlab.com/xx_network/primitives@release GOFLAGS="" go get -u gitlab.com/elixxir/primitives@release - GOFLAGS="" go get -u gitlab.com/elixxir/crypto@release GOFLAGS="" go get -u gitlab.com/xx_network/crypto@release + GOFLAGS="" go get -u gitlab.com/elixxir/crypto@release GOFLAGS="" go get -u gitlab.com/xx_network/comms@release GOFLAGS="" go get -u gitlab.com/elixxir/comms@release diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 0af89ff00d7f87f5590d55f4b2dfcd2788d013d0..88682af983c107345b6415648bec9558f2eb4d6b 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -20,16 +20,17 @@ import ( // RequestAuthenticatedChannel sends a request to another party to establish an // authenticated channel // It will not run if the network status is not healthy -// An error will be returned if a channel already exists, if a request was -// already received, or if a request was already sent +// An error will be returned if a channel already exists or if a request was +// already received // When a confirmation occurs, the channel will be created and the callback // will be called +// Can be retried. func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact, - message string) error { + message string) (id.Round, error) { jww.INFO.Printf("RequestAuthenticatedChannel(%s)", recipient.ID) if !c.network.GetHealthTracker().IsHealthy() { - return errors.New("Cannot request authenticated channel " + + return 0, errors.New("Cannot request authenticated channel " + "creation when the network is not healthy") } @@ -60,11 +61,12 @@ func (c *Client) GetAuthenticatedChannelRequest(partner *id.ID) (contact.Contact // An error will be returned if a channel already exists, if a request doest // exist, or if the passed in contact does not exactly match the received // request -func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) error { +// Can be retried. +func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) (id.Round, error) { jww.INFO.Printf("ConfirmAuthenticatedChannel(%s)", recipient.ID) if !c.network.GetHealthTracker().IsHealthy() { - return errors.New("Cannot request authenticated channel " + + return 0, errors.New("Cannot request authenticated channel " + "creation when the network is not healthy") } diff --git a/api/client.go b/api/client.go index eb12060227a9ae9cbcd8eef9d48ccbcb143cbebf..d3cb829b84b0f87b2743d259cfe884973cb070d4 100644 --- a/api/client.go +++ b/api/client.go @@ -84,33 +84,12 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str protoUser := createNewUser(rngStream, cmixGrp, e2eGrp) - // Get current client version - currentVersion, err := version.ParseVersion(SEMVER) - if err != nil { - return errors.WithMessage(err, "Could not parse version string.") - } - - // Create Storage - passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, protoUser, - currentVersion, cmixGrp, e2eGrp, rngStreamGen) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { return err } - // Save NDF to be used in the future - storageSess.SetBaseNDF(def) - - //store the registration code for later use - storageSess.SetRegCode(registrationCode) - - //move the registration state to keys generated - err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete) - if err != nil { - return errors.WithMessage(err, "Failed to denote state "+ - "change in session") - } - //TODO: close the session return nil } @@ -135,29 +114,39 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp) - // Get current client version - currentVersion, err := version.ParseVersion(SEMVER) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, true, "") if err != nil { - return errors.WithMessage(err, "Could not parse version string.") + return err } + //TODO: close the session + return nil +} - // Create Storage - passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, protoUser, - currentVersion, cmixGrp, e2eGrp, rngStreamGen) +// NewVanityClient creates a user with a receptionID that starts with the supplied prefix +// It creates client storage, generates keys, connects, and registers +// with the network. Note that this does not register a username/identity, but +// merely creates a new cryptographic identity for adding such information +// at a later date. +func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error { + jww.INFO.Printf("NewVanityClient()") + // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStream := rngStreamGen.GetStream() + + // Parse the NDF + def, err := parseNDF(ndfJSON) if err != nil { return err } + cmixGrp, e2eGrp := decodeGroups(def) - // Save NDF to be used in the future - storageSess.SetBaseNDF(def) + protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix) - //move the registration state to indicate registered with permissioning - err = storageSess.ForwardRegistrationStatus( - storage.PermissioningComplete) + err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { - return errors.WithMessage(err, "Failed to denote state "+ - "change in session") + return err } //TODO: close the session @@ -199,7 +188,7 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) ( return c, nil } -// Login initalizes a client object from existing storage. +// Login initializes a client object from existing storage. func Login(storageDir string, password []byte, parameters params.Network) (*Client, error) { jww.INFO.Printf("Login()") @@ -216,7 +205,7 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie //Attach the services interface c.services = newServiceProcessiesList(c.runner) - //initilize comms + // initialize comms err = c.initComms() if err != nil { return nil, err @@ -233,7 +222,7 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie } } else { jww.WARN.Printf("Registration with permissioning skipped due to " + - "blank permissionign address. Client will not be able to register " + + "blank permissioning address. Client will not be able to register " + "or track network.") } @@ -244,13 +233,7 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie return nil, err } - //update gateway connections - err = c.network.GetInstance().UpdateGatewayConnections() - if err != nil { - return nil, err - } - - //initilize the auth tracker + // initialize the auth tracker c.auth = auth.NewManager(c.switchboard, c.storage, c.network) return c, nil @@ -307,13 +290,7 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte, return nil, err } - //update gateway connections - err = c.network.GetInstance().UpdateGatewayConnections() - if err != nil { - return nil, err - } - - //initilize the auth tracker + // initialize the auth tracker c.auth = auth.NewManager(c.switchboard, c.storage, c.network) return c, nil @@ -354,7 +331,7 @@ func (c *Client) initPermissioning(def *ndf.NetworkDefinition) error { jww.ERROR.Printf("Client has failed registration: %s", err) return errors.WithMessage(err, "failed to load client") } - jww.INFO.Printf("Client sucsecfully registered with the network") + jww.INFO.Printf("Client successfully registered with the network") } return nil } @@ -458,7 +435,7 @@ func (c *Client) StopNetworkFollower(timeout time.Duration) error { return nil } -// Gets the state of the network follower. Returns: +// NetworkFollowerStatus Gets the state of the network follower. Returns: // Stopped - 0 // Starting - 1000 // Running - 2000 @@ -583,3 +560,43 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) { return cmixGrp, e2eGrp } + +// checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient +// it checks client version and creates a new storage for user data +func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, password []byte, + protoUser user.User, cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator, + isPrecanned bool, registrationCode string) error { + // Get current client version + currentVersion, err := version.ParseVersion(SEMVER) + if err != nil { + return errors.WithMessage(err, "Could not parse version string.") + } + + // Create Storage + passwordStr := string(password) + storageSess, err := storage.New(storageDir, passwordStr, protoUser, + currentVersion, cmixGrp, e2eGrp, rngStreamGen) + if err != nil { + return err + } + + // Save NDF to be used in the future + storageSess.SetBaseNDF(def) + + if !isPrecanned { + //store the registration code for later use + storageSess.SetRegCode(registrationCode) + //move the registration state to keys generated + err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete) + } else { + //move the registration state to indicate registered with permissioning + err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete) + } + + if err != nil { + return errors.WithMessage(err, "Failed to denote state "+ + "change in session") + } + + return nil +} diff --git a/api/results.go b/api/results.go index 885458fe2974d22b16592ba9e3f3ce2f5d4154a2..73d1193ccb9f0d3cb5667d6aa5a9170a2db78a5c 100644 --- a/api/results.go +++ b/api/results.go @@ -11,7 +11,6 @@ import ( "time" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/network/gateway" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/network" ds "gitlab.com/elixxir/comms/network/dataStructures" @@ -67,6 +66,8 @@ type historicalRoundsComm interface { func (c *Client) GetRoundResults(roundList []id.Round, timeout time.Duration, roundCallback RoundEventCallback) error { + jww.INFO.Printf("GetRoundResults(%v, %s)", roundList, timeout) + sendResults := make(chan ds.EventReturn, len(roundList)) return c.getRoundResults(roundList, timeout, roundCallback, @@ -91,6 +92,8 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration, allRoundsSucceeded := true numResults := 0 + oldestRound := networkInstance.GetOldestRoundID() + // Parse and adjudicate every round for _, rnd := range roundList { // Every round is timed out by default, until proven to have finished @@ -111,9 +114,7 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration, numResults++ } } else { - jww.DEBUG.Printf("Failed to ger round [%d] in buffer: %v", rnd, err) // Update oldest round (buffer may have updated externally) - oldestRound := networkInstance.GetOldestRoundID() if rnd < oldestRound { // If round is older that oldest round in our buffer // Add it to the historical round request (performed later) @@ -151,10 +152,12 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration, roundCallback(false, true, roundsResults) return case roundReport := <-sendResults: + numResults-- + // Skip if the round is nil (unknown from historical rounds) // they default to timed out, so correct behavior is preserved - if roundReport.RoundInfo == nil || roundReport.TimedOut { + if roundReport.RoundInfo == nil || roundReport.TimedOut { allRoundsSucceeded = false } else { // If available, denote the result @@ -177,33 +180,36 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration, // Helper function which asynchronously pings a random gateway until // it gets information on it's requested historical rounds func (c *Client) getHistoricalRounds(msg *pb.HistoricalRounds, - instance *network.Instance, sendResults chan ds.EventReturn, - comms historicalRoundsComm) { + instance *network.Instance, sendResults chan ds.EventReturn, comms historicalRoundsComm) { var resp *pb.HistoricalRoundsResponse - for { + //retry 5 times + for i := 0; i < 5; i++ { // Find a gateway to request about the roundRequests - gwHost, err := gateway.Get(instance.GetPartialNdf().Get(), comms, c.rng.GetStream()) - if err != nil { - jww.FATAL.Panicf("Failed to track network, NDF has corrupt "+ - "data: %s", err) - } + result, err := c.GetNetworkInterface().GetSender().SendToAny(func(host *connect.Host) (interface{}, error) { + return comms.RequestHistoricalRounds(host, msg) + }) // If an error, retry with (potentially) a different gw host. // If no error from received gateway request, exit loop // and process rounds - resp, err = comms.RequestHistoricalRounds(gwHost, msg) if err == nil { + resp = result.(*pb.HistoricalRoundsResponse) break + } else { + jww.ERROR.Printf("Failed to lookup historical rounds: %s", err) } } + if resp == nil{ + return + } + // Process historical rounds, sending back to the caller thread for _, ri := range resp.Rounds { sendResults <- ds.EventReturn{ - ri, - false, + RoundInfo: ri, } } } diff --git a/api/user.go b/api/user.go index 892d84c63c8fb0e29001774c6c645f351b3d00b8..48d7f992dfa32b48a3905752c55459ddc710e701 100644 --- a/api/user.go +++ b/api/user.go @@ -17,6 +17,10 @@ import ( "gitlab.com/xx_network/crypto/xx" "gitlab.com/xx_network/primitives/id" "math/rand" + "regexp" + "runtime" + "strings" + "sync" ) const ( @@ -134,3 +138,126 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic. ReceptionRSA: rsaKey, } } + +// createNewVanityUser generates an identity for cMix +// The identity's ReceptionID is not random but starts with the supplied prefix +func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User { + // CMIX Keygen + // FIXME: Why 256 bits? -- this is spec but not explained, it has + // to do with optimizing operations on one side and still preserves + // decent security -- cite this. + cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // DH Keygen + // FIXME: Why 256 bits? -- this is spec but not explained, it has + // to do with optimizing operations on one side and still preserves + // decent security -- cite this. Why valid for BOTH e2e and cmix? + e2eKeyBytes, err := csprng.GenerateInGroup(e2e.GetPBytes(), 256, rng) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // RSA Keygen (4096 bit defaults) + transmissionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + // Salt, UID, etc gen + transmissionSalt := make([]byte, SaltSize) + n, err := csprng.NewSystemRNG().Read(transmissionSalt) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("transmissionSalt size too small: %d", n) + } + transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + receptionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + var mu sync.Mutex // just in case more than one go routine tries to access receptionSalt and receptionID + done := make(chan struct{}) + found:= make(chan bool) + wg:= &sync.WaitGroup{} + cores := runtime.NumCPU() + + var receptionSalt []byte + var receptionID *id.ID + + pref := prefix + ignoreCase := false + // check if case-insensitivity is enabled + if strings.HasPrefix(prefix, "(?i)") { + pref = strings.ToLower(pref[4:]) + ignoreCase = true + } + // Check if prefix contains valid Base64 characters + match, _ := regexp.MatchString("^[A-Za-z0-9+/]+$", pref) + if match == false { + jww.FATAL.Panicf("Prefix contains non-Base64 characters") + } + jww.INFO.Printf("Vanity userID generation started. Prefix: %s Ignore-Case: %v NumCPU: %d", pref, ignoreCase, cores) + for w := 0; w < cores; w++{ + wg.Add(1) + go func() { + rSalt := make([]byte, SaltSize) + for { + select { + case <- done: + defer wg.Done() + return + default: + n, err = csprng.NewSystemRNG().Read(rSalt) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("receptionSalt size too small: %d", n) + } + rID, err := xx.NewID(receptionRsaKey.GetPublic(), rSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + id := rID.String() + if ignoreCase { + id = strings.ToLower(id) + } + if strings.HasPrefix(id, pref) { + mu.Lock() + receptionID = rID + receptionSalt = rSalt + mu.Unlock() + found <- true + defer wg.Done() + return + } + } + } + }() + } + // wait for a solution then close the done channel to signal the workers to exit + <- found + close(done) + wg.Wait() + return user.User{ + TransmissionID: transmissionID.DeepCopy(), + TransmissionSalt: transmissionSalt, + TransmissionRSA: transmissionRsaKey, + ReceptionID: receptionID.DeepCopy(), + ReceptionSalt: receptionSalt, + ReceptionRSA: receptionRsaKey, + Precanned: false, + CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes), + E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), + } +} diff --git a/api/utilsInterfaces_test.go b/api/utilsInterfaces_test.go index ffa5154795045f361afd70d8cb67d0c45eb6e4c4..62ab16f5231d0bdc81ab30f8e0a1b890fb0931c7 100644 --- a/api/utilsInterfaces_test.go +++ b/api/utilsInterfaces_test.go @@ -10,6 +10,7 @@ import ( "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/stoppable" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/network" @@ -79,6 +80,7 @@ func (ht *historicalRounds) GetHost(hostId *id.ID) (*connect.Host, bool) { // Contains a test implementation of the networkManager interface. type testNetworkManagerGeneric struct { instance *network.Instance + sender *gateway.Sender } /* Below methods built for interface adherence */ @@ -119,3 +121,7 @@ func (t *testNetworkManagerGeneric) GetStoppable() stoppable.Stoppable { func (t *testNetworkManagerGeneric) InProgressRegistrations() int { return 0 } + +func (t *testNetworkManagerGeneric) GetSender() *gateway.Sender { + return t.sender +} diff --git a/api/utils_test.go b/api/utils_test.go index ab2079ba39782e558cde5ff78ca2cd7f678fa217..a20f1e0bf64a0da34935d467ac483caccd742dce 100644 --- a/api/utils_test.go +++ b/api/utils_test.go @@ -8,6 +8,7 @@ package api import ( + "gitlab.com/elixxir/client/network/gateway" "testing" "github.com/pkg/errors" @@ -64,7 +65,10 @@ func newTestingClient(face interface{}) (*Client, error) { return nil, err } - c.network = &testNetworkManagerGeneric{instance: thisInstance} + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + sender, _ := gateway.NewSender(p, c.rng, def, commsManager, c.storage, nil) + c.network = &testNetworkManagerGeneric{instance: thisInstance, sender: sender} return c, nil } diff --git a/api/version_vars.go b/api/version_vars.go index 1a4ed3b5336c44eeed1cf474d589c1175808c16a..fa844ec2db29fa3a5d5899abb0adbfaa043a1a03 100644 --- a/api/version_vars.go +++ b/api/version_vars.go @@ -1,10 +1,10 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2021-04-02 08:56:27.8657223 -0700 PDT m=+0.054483401 +// 2021-04-29 12:54:02.223688 -0500 CDT m=+0.028208749 package api -const GITVERSION = `6020ab79 removed an extranious print` -const SEMVER = "2.3.0" +const GITVERSION = `9c8c08ea prevent updating with an empty ndf` +const SEMVER = "2.4.0" const DEPENDENCIES = `module gitlab.com/elixxir/client go 1.13 @@ -13,28 +13,26 @@ require ( github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang/protobuf v1.4.3 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect - github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea - github.com/magiconair/properties v1.8.4 // indirect - github.com/mitchellh/mapstructure v1.4.0 // indirect - github.com/pelletier/go-toml v1.8.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/pelletier/go-toml v1.9.0 // indirect github.com/pkg/errors v0.9.1 - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/smartystreets/assertions v1.0.1 // indirect - github.com/spf13/afero v1.5.1 // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.1.1 + github.com/spf13/cobra v1.1.3 github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.4-0.20210401210158-6053ad2e224c - gitlab.com/elixxir/crypto v0.0.7-0.20210401210040-b7f1da24ef13 - gitlab.com/elixxir/ekv v0.1.4 - gitlab.com/elixxir/primitives v0.0.3-0.20210401175645-9b7b92f74ec4 - gitlab.com/xx_network/comms v0.0.4-0.20210401160731-7b8890cdd8ad - gitlab.com/xx_network/crypto v0.0.5-0.20210401160648-4f06cace9123 - gitlab.com/xx_network/primitives v0.0.4-0.20210331161816-ed23858bdb93 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad - golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect + gitlab.com/elixxir/comms v0.0.4-0.20210427005410-7ae183abda2a + gitlab.com/elixxir/crypto v0.0.7-0.20210427005255-4fe1bcf69c5a + gitlab.com/elixxir/ekv v0.1.5 + gitlab.com/elixxir/primitives v0.0.3-0.20210427004615-c68ecf15fcf3 + gitlab.com/xx_network/comms v0.0.4-0.20210426213447-82674e09e402 + gitlab.com/xx_network/crypto v0.0.5-0.20210420170153-2a6276844076 + gitlab.com/xx_network/primitives v0.0.4-0.20210402222416-37c1c4d3fac4 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect + golang.org/x/text v0.3.6 // indirect google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect google.golang.org/grpc v1.34.0 // indirect google.golang.org/protobuf v1.26.0-rc.1 diff --git a/auth/callback.go b/auth/callback.go index ef21512f88a06c19322c03518bc262d7d2e94ebe..2c39824cb8631b222d6140fd66d2e9f143873639 100644 --- a/auth/callback.go +++ b/auth/callback.go @@ -213,7 +213,7 @@ func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if mgr, err := m.storage.E2e().GetPartner(sr.GetPartner()); mgr != nil || err == nil { jww.WARN.Printf("Cannot confirm auth for %s, channel already "+ "exists.", sr.GetPartner()) - m.storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Done(sr.GetPartner()) return } @@ -221,7 +221,7 @@ func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) if err != nil { jww.WARN.Printf("Failed to handle auth confirm: %s", err) - m.storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Done(sr.GetPartner()) return } @@ -236,7 +236,7 @@ func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if !success { jww.WARN.Printf("Recieved auth confirmation failed its mac " + "check") - m.storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Done(sr.GetPartner()) return } @@ -244,7 +244,7 @@ func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if err != nil { jww.WARN.Printf("Failed to unmarshal auth confirmation's "+ "encrypted payload: %s", err) - m.storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Done(sr.GetPartner()) return } @@ -252,7 +252,7 @@ func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if err := m.doConfirm(sr, grp, partnerPubKey, sr.GetMyPrivKey(), sr.GetPartnerHistoricalPubKey(), ecrFmt.GetOwnership()); err != nil { jww.WARN.Printf("Confirmation failed: %s", err) - m.storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Done(sr.GetPartner()) return } } diff --git a/auth/confirm.go b/auth/confirm.go index fee1433a2c22b7323cb5c1ce9d1ea57c6108c9a4..40c983c6feb73446495bb9307a2c6793fca7b259 100644 --- a/auth/confirm.go +++ b/auth/confirm.go @@ -12,26 +12,23 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/interfaces/utility" "gitlab.com/elixxir/client/storage" - ds "gitlab.com/elixxir/comms/network/dataStructures" + "gitlab.com/xx_network/primitives/id" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/diffieHellman" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" "io" - "time" ) func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, - storage *storage.Session, net interfaces.NetworkManager) error { + storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) { /*edge checking*/ // check that messages can be sent over the network if !net.GetHealthTracker().IsHealthy() { - return errors.New("Cannot confirm authenticated message " + + return 0, errors.New("Cannot confirm authenticated message " + "when the network is not healthy") } @@ -40,14 +37,15 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, // the lock storedContact, err := storage.Auth().GetReceivedRequest(partner.ID) if err != nil { - return errors.Errorf("failed to find a pending Auth Request: %s", + return 0, errors.Errorf("failed to find a pending Auth Request: %s", err) } + defer storage.Auth().Done(partner.ID) // verify the passed contact matches what is stored if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 { - storage.Auth().Fail(partner.ID) - return errors.WithMessage(err, "Pending Auth Request has different "+ + storage.Auth().Done(partner.ID) + return 0, errors.WithMessage(err, "Pending Auth Request has different "+ "pubkey than stored") } @@ -67,8 +65,7 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, salt := make([]byte, saltSize) _, err = rng.Read(salt) if err != nil { - storage.Auth().Fail(partner.ID) - return errors.Wrap(err, "Failed to generate salt for "+ + return 0, errors.Wrap(err, "Failed to generate salt for "+ "confirmation") } @@ -107,22 +104,19 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, p := storage.E2e().GetE2ESessionParams() if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey, newPrivKey, p, p); err != nil { - storage.Auth().Fail(partner.ID) - return errors.Errorf("Failed to create channel with partner (%s) "+ - "on confirmation: %+v", - partner.ID, err) + jww.WARN.Printf("Failed to create channel with partner (%s) "+ + "on confirmation, this is likley a replay: %s", + partner.ID, err.Error()) } // delete the in progress negotiation // this unlocks the request lock - if err := storage.Auth().Delete(partner.ID); err != nil { - return errors.Errorf("UNRECOVERABLE! Failed to delete in "+ + //fixme - do these deletes at a later date + /*if err := storage.Auth().Delete(partner.ID); err != nil { + return 0, errors.Errorf("UNRECOVERABLE! Failed to delete in "+ "progress negotiation with partner (%s) after creating confirmation: %+v", partner.ID, err) - } - - //store the message as a critical message so it will always be sent - storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID) + }*/ jww.INFO.Printf("Confirming Auth with %s, msgDigest: %s", partner.ID, cmixMsg.Digest()) @@ -134,39 +128,11 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, // retried jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ "to transmit: %+v", partner.ID, cmixMsg.Digest(), err) - storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID) - return errors.WithMessage(err, "Auth Confirm Failed to transmit") + return 0, errors.WithMessage(err, "Auth Confirm Failed to transmit") } jww.INFO.Printf("Confirm Request with %s (msgDigest: %s) sent on round %d", partner.ID, cmixMsg.Digest(), round) - /*check message delivery*/ - sendResults := make(chan ds.EventReturn, 1) - roundEvents := net.GetInstance().GetRoundEvents() - - roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute, - states.COMPLETED, states.FAILED) - - success, numFailed, _ := utility.TrackResults(sendResults, 1) - if !success { - if numFailed > 0 { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ - "delivery due to round failure, will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } else { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ - "delivery due to timeout, will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } - jww.ERROR.Printf("auth confirm failed to transmit, will be " + - "handled on reconnect") - storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID) - } else { - jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) delivered "+ - "sucesfully", partner.ID, cmixMsg.Digest()) - storage.GetCriticalRawMessages().Succeeded(cmixMsg, partner.ID) - } - - return nil + return round, nil } diff --git a/auth/request.go b/auth/request.go index 7c963123b49dd532734871c687277a318887093d..c43707904f835b19d1e64ba53cb4ec9fe550355f 100644 --- a/auth/request.go +++ b/auth/request.go @@ -12,59 +12,59 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/interfaces/utility" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/auth" "gitlab.com/elixxir/client/storage/e2e" - ds "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/diffieHellman" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/primitives/format" - "gitlab.com/elixxir/primitives/states" + "gitlab.com/xx_network/primitives/id" "io" "strings" - "time" ) const terminator = ";" func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, - storage *storage.Session, net interfaces.NetworkManager) error { + storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) { /*edge checks generation*/ // check that an authenticated channel does not already exists if _, err := storage.E2e().GetPartner(partner.ID); err == nil || !strings.Contains(err.Error(), e2e.NoPartnerErrorStr) { - return errors.Errorf("Authenticated channel already " + + return 0, errors.Errorf("Authenticated channel already " + "established with partner") } // check that the request is being sent from the proper ID if !me.ID.Cmp(storage.GetUser().ReceptionID) { - return errors.Errorf("Authenticated channel request " + + return 0, errors.Errorf("Authenticated channel request " + "can only be sent from user's identity") } // check that the message is properly formed if strings.Contains(message, terminator) { - return errors.Errorf("Message cannot contain '%s'", terminator) + return 0, errors.Errorf("Message cannot contain '%s'", terminator) } + //denote if this is a resend of an old request + resend := false + //lookup if an ongoing request is occurring - rqType, _, _, err := storage.Auth().GetRequest(partner.ID) - if err != nil && strings.Contains(err.Error(), auth.NoRequest) { - err = nil - } - if err != nil { + rqType, sr, _, err := storage.Auth().GetRequest(partner.ID) + + if err != nil && !strings.Contains(err.Error(), auth.NoRequest){ if rqType == auth.Receive { - return errors.WithMessage(err, - "Cannot send a request after "+ - "receiving a request") + return 0, errors.WithMessage(err, + "Cannot send a request after receiving a request") } else if rqType == auth.Sent { - return errors.WithMessage(err, - "Cannot send a request after "+ - "already sending one") + resend = true + }else{ + return 0, errors.WithMessage(err, + "Cannot send a request after receiving unknown error " + + "on requesting contact status") } } @@ -76,7 +76,7 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen()) requestFmt, err := newRequestFormat(ecrFmt) if err != nil { - return errors.Errorf("failed to make request format: %+v", err) + return 0, errors.Errorf("failed to make request format: %+v", err) } //check the payload fits @@ -85,7 +85,7 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, msgPayloadBytes := []byte(msgPayload) if len(msgPayloadBytes) > requestFmt.MsgPayloadLen() { - return errors.Errorf("Combined message longer than space "+ + return 0, errors.Errorf("Combined message longer than space "+ "available in payload; available: %v, length: %v", requestFmt.MsgPayloadLen(), len(msgPayloadBytes)) } @@ -95,17 +95,27 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, salt := make([]byte, saltSize) _, err = rng.Read(salt) if err != nil { - return errors.Wrap(err, "Failed to generate salt") + return 0, errors.Wrap(err, "Failed to generate salt") + } + + var newPrivKey, newPubKey *cyclic.Int + + // in this case we have an ongoing request so we can resend the extant + // request + if resend{ + newPrivKey = sr.GetMyPrivKey() + newPubKey = sr.GetMyPubKey() + //in this case it is a new request and we must generate new keys + }else{ + //generate new keypair + newPrivKey = diffieHellman.GeneratePrivateKey(256, grp, rng) + newPubKey = diffieHellman.GeneratePublicKey(newPrivKey, grp) } //generate ownership proof ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(), partner.DhPubKey, storage.E2e().GetGroup()) - //generate new keypair - newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng) - newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp) - jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", newPubKey.Bytes()) jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v", partner.DhPubKey.Bytes()) @@ -130,67 +140,30 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, /*store state*/ //fixme: channel is bricked if the first store succedes but the second fails //store the in progress auth - err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, - newPrivKey, confirmFp) - if err != nil { - return errors.Errorf("Failed to store auth request: %s", err) + if !resend{ + err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, + newPrivKey, confirmFp) + if err != nil { + return 0, errors.Errorf("Failed to store auth request: %s", err) + } } - //store the message as a critical message so it will always be sent - storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID) + jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s", + partner.ID, cmixMsg.Digest()) - go func() { - jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s", - partner.ID, cmixMsg.Digest()) - - /*send message*/ - round, _, err := net.SendCMIX(cmixMsg, partner.ID, - params.GetDefaultCMIX()) - if err != nil { - // if the send fails just set it to failed, it will - // but automatically retried - jww.WARN.Printf("Auth Request with %s (msgDigest: %s)"+ - " failed to transmit: %+v", partner.ID, - cmixMsg.Digest(), err) - storage.GetCriticalRawMessages().Failed(cmixMsg, - partner.ID) - } + /*send message*/ + round, _, err := net.SendCMIX(cmixMsg, partner.ID, + params.GetDefaultCMIX()) + if err != nil { + // if the send fails just set it to failed, it will + // but automatically retried + return 0, errors.WithMessagef(err, "Auth Request with %s " + + "(msgDigest: %s) failed to transmit: %+v", partner.ID, + cmixMsg.Digest(), err) + } - jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent"+ - " on round %d", partner.ID, cmixMsg.Digest(), round) - - /*check message delivery*/ - sendResults := make(chan ds.EventReturn, 1) - roundEvents := net.GetInstance().GetRoundEvents() - - roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute, - states.COMPLETED, states.FAILED) - - success, numFailed, _ := utility.TrackResults(sendResults, 1) - if !success { - if numFailed > 0 { - jww.WARN.Printf("Auth Request with %s "+ - "(msgDigest: %s) failed "+ - "delivery due to round failure, "+ - "will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } else { - jww.WARN.Printf("Auth Request with %s "+ - "(msgDigest: %s) failed "+ - "delivery due to timeout, "+ - "will retry on reconnect", - partner.ID, cmixMsg.Digest()) - } - storage.GetCriticalRawMessages().Failed(cmixMsg, - partner.ID) - } else { - jww.INFO.Printf("Auth Request with %s (msgDigest: %s) "+ - "delivered sucessfully", partner.ID, - cmixMsg.Digest()) - storage.GetCriticalRawMessages().Succeeded(cmixMsg, - partner.ID) - } - }() + jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent"+ + " on round %d", partner.ID, cmixMsg.Digest(), round) - return nil + return round, nil } diff --git a/bindings/authenticatedChannels.go b/bindings/authenticatedChannels.go index 28784c87f944bd3d15e43b873194f968dd513971..30b12f0a1b3039fba5b18eafeff874cc7a438a60 100644 --- a/bindings/authenticatedChannels.go +++ b/bindings/authenticatedChannels.go @@ -27,19 +27,20 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID int) (*Contact, e // authenticated channel // It will not run if the network status is not healthy // An error will be returned if a channel already exists, if a request was -// already received, or if a request was already sent +// already received. // When a confirmation occurs, the channel will be created and the callback // will be called +// This can be called many times and retried. // // This function takes the marshaled send report to ensure a memory leak does // not occur as a result of both sides of the bindings holding a refrence to // the same pointer. func (c *Client) RequestAuthenticatedChannel(recipientMarshaled, - meMarshaled []byte, message string) error { + meMarshaled []byte, message string) (int, error) { recipent, err := contact.Unmarshal(recipientMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "RequestAuthenticatedChannel: Failed to Unmarshal Recipent: "+ "%+v", err)) } @@ -47,11 +48,13 @@ func (c *Client) RequestAuthenticatedChannel(recipientMarshaled, me, err := contact.Unmarshal(meMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "RequestAuthenticatedChannel: Failed to Unmarshal Me: %+v", err)) } - return c.api.RequestAuthenticatedChannel(recipent, me, message) + rid, err := c.api.RequestAuthenticatedChannel(recipent, me, message) + + return int(rid), err } // RegisterAuthCallbacks registers both callbacks for authenticated channels. @@ -79,19 +82,26 @@ func (c *Client) RegisterAuthCallbacks(request AuthRequestCallback, // received request and sends a message to the requestor that the request has // been confirmed // It will not run if the network status is not healthy -// An error will be returned if a channel already exists, if a request doest +// An error will be returned if a request doest // exist, or if the passed in contact does not exactly match the received -// request -func (c *Client) ConfirmAuthenticatedChannel(recipientMarshaled []byte) error { +// request. +// This can be called many times and retried. +// +// This function takes the marshaled send report to ensure a memory leak does +// not occur as a result of both sides of the bindings holding a refrence to +// the same pointer. +func (c *Client) ConfirmAuthenticatedChannel(recipientMarshaled []byte) (int, error) { recipent, err := contact.Unmarshal(recipientMarshaled) if err != nil { - return errors.New(fmt.Sprintf("Failed to "+ + return 0, errors.New(fmt.Sprintf("Failed to "+ "ConfirmAuthenticatedChannel: Failed to Unmarshal Recipient: "+ "%+v", err)) } - return c.api.ConfirmAuthenticatedChannel(recipent) + rid, err := c.api.ConfirmAuthenticatedChannel(recipent) + + return int(rid), err } // VerifyOwnership checks if the ownership proof on a passed contact matches the diff --git a/bindings/callback.go b/bindings/callback.go index d0fe898c204188646c74c647f41759b5988cbf69..a6526d24d3ba961db2b79bdc2fbd6a1b950821dc 100644 --- a/bindings/callback.go +++ b/bindings/callback.go @@ -31,12 +31,19 @@ type NetworkHealthCallback interface { Callback(bool) } -// RoundEventHandler handles round events happening on the cMix network. +// RoundEventCallback handles waiting on the exact state of a round on +// the cMix network. type RoundEventCallback interface { EventCallback(rid, state int, timedOut bool) } -// RoundEventHandler handles round events happening on the cMix network. +// RoundCompletionCallback is returned when the completion of a round is known. +type RoundCompletionCallback interface { + EventCallback(rid int, success, timedOut bool) +} + +// MessageDeliveryCallback gets called on the determination if all events +// related to a message send were successful. type MessageDeliveryCallback interface { EventCallback(msgID []byte, delivered, timedOut bool, roundResults []byte) } diff --git a/bindings/client.go b/bindings/client.go index 34fb521076fca455294824ef3104f1c67445669d..d5c40e456de1b263a48184250cbb67fecc9e0c88 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -8,7 +8,6 @@ package bindings import ( - "encoding/json" "errors" "fmt" jww "github.com/spf13/jwalterweatherman" @@ -157,11 +156,7 @@ func UnmarshalContact(b []byte) (*Contact, error) { //Unmarshals a marshaled send report object, returns an error if it fails func UnmarshalSendReport(b []byte) (*SendReport, error) { sr := &SendReport{} - if err := json.Unmarshal(b, sr); err != nil { - return nil, errors.New(fmt.Sprintf("Failed to Unmarshal "+ - "Send Report: %+v", err)) - } - return sr, nil + return sr, sr.Unmarshal(b) } // StartNetworkFollower kicks off the tracking of the network. It starts @@ -333,29 +328,55 @@ func (c *Client) RegisterRoundEventsHandler(rid int, cb RoundEventCallback, return newRoundUnregister(roundID, ec, c.api.GetRoundEvents()) } -// RegisterMessageDeliveryCB allows the caller to get notified if the rounds a -// message was sent in successfully completed. Under the hood, this uses the same -// interface as RegisterRoundEventsHandler, but provides a convenient way to use -// the interface in its most common form, looking up the result of message -// retrieval +// WaitForRoundCompletion allows the caller to get notified if a round +// has completed (or failed). Under the hood, this uses an API which uses the internal +// round data, network historical round lookup, and waiting on network events +// to determine what has (or will) occur. +// +// The callbacks will return at timeoutMS if no state update occurs +func (c *Client) WaitForRoundCompletion(roundID int, + rec RoundCompletionCallback, timeoutMS int) error { + + f := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]api.RoundResult) { + rec.EventCallback(roundID, allRoundsSucceeded, timedOut) + } + + timeout := time.Duration(timeoutMS) * time.Millisecond + + return c.api.GetRoundResults([]id.Round{id.Round(roundID)}, timeout, f) +} + +// WaitForMessageDelivery allows the caller to get notified if the rounds a +// message was sent in successfully completed. Under the hood, this uses an API +// which uses the internal round data, network historical round lookup, and +// waiting on network events to determine what has (or will) occur. // // The callbacks will return at timeoutMS if no state update occurs // // This function takes the marshaled send report to ensure a memory leak does // not occur as a result of both sides of the bindings holding a reference to // the same pointer. -func (c *Client) WaitForRoundCompletion(marshaledSendReport []byte, +func (c *Client) WaitForMessageDelivery(marshaledSendReport []byte, mdc MessageDeliveryCallback, timeoutMS int) error { - + jww.INFO.Printf("WaitForMessageDelivery(%v, _, %v)", + marshaledSendReport, timeoutMS) sr, err := UnmarshalSendReport(marshaledSendReport) if err != nil { return errors.New(fmt.Sprintf("Failed to "+ - "WaitForRoundCompletion callback due to bad Send Report: %+v", err)) + "WaitForMessageDelivery callback due to bad Send Report: %+v", err)) + } + + if sr==nil || sr.rl == nil || len(sr.rl.list) == 0{ + return errors.New(fmt.Sprintf("Failed to "+ + "WaitForMessageDelivery callback due to invalid Send Report " + + "unmarshal: %s", string(marshaledSendReport))) } f := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]api.RoundResult) { results := make([]byte, len(sr.rl.list)) - + jww.INFO.Printf("Processing WaitForMessageDelivery report " + + "for %v, success: %v, timedout: %v", sr.mid, allRoundsSucceeded, + timedOut) for i, r := range sr.rl.list { if result, exists := rounds[r]; exists { results[i] = byte(result) @@ -367,7 +388,9 @@ func (c *Client) WaitForRoundCompletion(marshaledSendReport []byte, timeout := time.Duration(timeoutMS) * time.Millisecond - return c.api.GetRoundResults(sr.rl.list, timeout, f) + err = c.api.GetRoundResults(sr.rl.list, timeout, f) + + return err } // Returns a user object from which all information about the current user diff --git a/bindings/message.go b/bindings/message.go index 978a62743dbd823062f1c4a961e20d74f386c496..6cead4ad09a8be5fc49433bf9e68e5cf0ab87a9f 100644 --- a/bindings/message.go +++ b/bindings/message.go @@ -7,7 +7,9 @@ package bindings -import "gitlab.com/elixxir/client/interfaces/message" +import ( + "gitlab.com/elixxir/client/interfaces/message" +) // Message is a message received from the cMix network in the clear // or that has been decrypted using established E2E keys. @@ -36,10 +38,12 @@ func (m *Message) GetMessageType() int { } // Returns the message's timestamp in ms -func (m *Message) GetTimestampMS() int { - return int(m.r.Timestamp.Unix()) +func (m *Message) GetTimestampMS() int64 { + ts := m.r.Timestamp.UnixNano() + ts = (ts+999999)/1000000 + return ts } -func (m *Message) GetTimestampNano() int { - return int(m.r.Timestamp.UnixNano()) +func (m *Message) GetTimestampNano() int64 { + return m.r.Timestamp.UnixNano() } diff --git a/bindings/send.go b/bindings/send.go index a7e35873447758c22cb9ddfc8eddeaf86fe1bb73..673fad0003ac009b707de67be8f3edc9804f639f 100644 --- a/bindings/send.go +++ b/bindings/send.go @@ -135,6 +135,11 @@ type SendReport struct { mid e2e.MessageID } +type SendReportDisk struct{ + List []id.Round + Mid []byte +} + func (sr *SendReport) GetRoundList() *RoundList { return sr.rl } @@ -144,5 +149,22 @@ func (sr *SendReport) GetMessageID() []byte { } func (sr *SendReport) Marshal() ([]byte, error) { - return json.Marshal(sr) + srd := SendReportDisk{ + List: sr.rl.list, + Mid: sr.mid[:], + } + return json.Marshal(&srd) +} + +func (sr *SendReport) Unmarshal(b []byte) error { + srd := SendReportDisk{ + } + if err := json.Unmarshal(b, &srd); err!=nil{ + return errors.New(fmt.Sprintf("Failed to unmarshal send " + + "report: %s", err.Error())) + } + + copy(sr.mid[:],srd.Mid) + sr.rl = &RoundList{list:srd.List} + return nil } diff --git a/cmd/getndf.go b/cmd/getndf.go index 914412093c8f1247953473c29a02edcd68346235..7ac150020c911d695f646993e3a409c18d6cb8da 100644 --- a/cmd/getndf.go +++ b/cmd/getndf.go @@ -18,11 +18,13 @@ import ( // "gitlab.com/elixxir/client/switchboard" // "gitlab.com/elixxir/client/ud" // "gitlab.com/elixxir/primitives/fact" + "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/comms/client" "gitlab.com/xx_network/comms/connect" //"time" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/utils" ) @@ -63,12 +65,14 @@ var getNDFCmd = &cobra.Command{ if gwHost != "" { host, _ := connect.NewHost(&id.TempGateway, gwHost, cert, params) + dummyID := ephemeral.ReservedIDs[0] pollMsg := &pb.GatewayPoll{ Partial: &pb.NDFHash{ Hash: nil, }, LastUpdate: uint64(0), - ReceptionID: id.DummyUser.Marshal(), + ReceptionID: dummyID[:], + ClientVersion: []byte(api.SEMVER), } resp, err := comms.SendPoll(host, pollMsg) if err != nil { diff --git a/cmd/init.go b/cmd/init.go index cf19521f066d1e9e878524a7ee75fd3471cf6b7c..b00f215475bfba0c513752b1d2fd01fd13cea5c8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -11,6 +11,7 @@ package cmd import ( "fmt" "github.com/spf13/cobra" + "github.com/spf13/viper" jww "github.com/spf13/jwalterweatherman" ) @@ -29,5 +30,9 @@ var initCmd = &cobra.Command{ } func init() { + initCmd.Flags().StringP("userid-prefix", "", "", + "Desired prefix of userID to brute force when running init command. Prepend (?i) for case-insensitive. Only Base64 characters are valid.") + _ = viper.BindPFlag("userid-prefix", initCmd.Flags().Lookup("userid-prefix")) + rootCmd.AddCommand(initCmd) } diff --git a/cmd/root.go b/cmd/root.go index 1c7c2166ef55eac800f384c35fdffe710c288aed..fb14badf09039bbfd31e08c341e7352a5fb2d68d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -78,7 +78,7 @@ var rootCmd = &cobra.Command{ jww.INFO.Printf("Message ListenerID: %v", listenerID) // Set up auth request handler, which simply prints the - // user id of the requestor. + // user id of the requester. authMgr := client.GetAuthRegistrar() authMgr.AddGeneralRequestCallback(printChanRequest) @@ -95,7 +95,7 @@ var rootCmd = &cobra.Command{ requestor contact.Contact, message string) { jww.INFO.Printf("Channel Request: %s", requestor.ID) - err := client.ConfirmAuthenticatedChannel( + _, err := client.ConfirmAuthenticatedChannel( requestor) if err != nil { jww.FATAL.Panicf("%+v", err) @@ -163,7 +163,7 @@ var rootCmd = &cobra.Command{ } if !unsafe && !authConfirmed { - jww.INFO.Printf("Waiting for authentication channel "+ + jww.INFO.Printf("Waiting for authentication channel"+ " confirmation with partner %s", recipientID) scnt := uint(0) waitSecs := viper.GetUint("auth-timeout") @@ -306,7 +306,7 @@ func createClient() *api.Client { storeDir := viper.GetString("session") regCode := viper.GetString("regcode") precannedID := viper.GetUint("sendid") - + userIDprefix := viper.GetString("userid-prefix") //create a new client if none exist if _, err := os.Stat(storeDir); os.IsNotExist(err) { // Load NDF @@ -320,8 +320,14 @@ func createClient() *api.Client { err = api.NewPrecannedClient(precannedID, string(ndfJSON), storeDir, []byte(pass)) } else { - err = api.NewClient(string(ndfJSON), storeDir, + if userIDprefix != "" { + err = api.NewVanityClient(string(ndfJSON), storeDir, + []byte(pass), regCode, userIDprefix) + } else { + err = api.NewClient(string(ndfJSON), storeDir, []byte(pass), regCode) + } + } if err != nil { @@ -401,7 +407,7 @@ func acceptChannel(client *api.Client, recipientID *id.ID) { if err != nil { jww.FATAL.Panicf("%+v", err) } - err = client.ConfirmAuthenticatedChannel( + _, err = client.ConfirmAuthenticatedChannel( recipientContact) if err != nil { jww.FATAL.Panicf("%+v", err) @@ -463,7 +469,7 @@ func addAuthenticatedChannel(client *api.Client, recipientID *id.ID, me := client.GetUser().GetContact() jww.INFO.Printf("Requesting auth channel from: %s", recipientID) - err := client.RequestAuthenticatedChannel(recipientContact, + _, err := client.RequestAuthenticatedChannel(recipientContact, me, msg) if err != nil { jww.FATAL.Panicf("%+v", err) diff --git a/cmd/single.go b/cmd/single.go index 7fe8e2038ef34240bd2ee363e6f38d8d94d26ed9..15f803b11bec0f775845fdfdfb9f8292c75ea2a7 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -55,7 +55,7 @@ var singleCmd = &cobra.Command{ authMgr.AddGeneralRequestCallback(func( requester contact.Contact, message string) { jww.INFO.Printf("Got request: %s", requester.ID) - err := client.ConfirmAuthenticatedChannel(requester) + _, err := client.ConfirmAuthenticatedChannel(requester) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/ud.go b/cmd/ud.go index ccefe891a4e87c2053ec62fd95e835115aa21a45..38cbecb2cd1c6572cd140b158f435fe4291500cd 100644 --- a/cmd/ud.go +++ b/cmd/ud.go @@ -55,7 +55,7 @@ var udCmd = &cobra.Command{ authMgr.AddGeneralRequestCallback(func( requester contact.Contact, message string) { jww.INFO.Printf("Got Request: %s", requester.ID) - err := client.ConfirmAuthenticatedChannel(requester) + _, err := client.ConfirmAuthenticatedChannel(requester) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/version.go b/cmd/version.go index 2a7eca16517f8e7f3d5c547ea35dbc472d4607c5..b781e502cb97afc8edd8c90300c8a9f1e5816969 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,7 +18,7 @@ import ( ) // Change this value to set the version for this build -const currentVersion = "2.3.0" +const currentVersion = "2.4.0" func Version() string { out := fmt.Sprintf("Elixxir Client v%s -- %s\n\n", api.SEMVER, diff --git a/go.mod b/go.mod index 47671e792ba3599c48275a617a133c3a9005fd28..20c85a77446311ad7223fb314e0e7bbfa0aa5108 100644 --- a/go.mod +++ b/go.mod @@ -6,28 +6,26 @@ require ( github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang/protobuf v1.4.3 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect - github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea // indirect - github.com/magiconair/properties v1.8.4 // indirect - github.com/mitchellh/mapstructure v1.4.0 // indirect - github.com/pelletier/go-toml v1.8.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/pelletier/go-toml v1.9.0 // indirect github.com/pkg/errors v0.9.1 - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/smartystreets/assertions v1.0.1 // indirect - github.com/spf13/afero v1.5.1 // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.1.1 + github.com/spf13/cobra v1.1.3 github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.4-0.20210414225623-fb1a8a82f210 - gitlab.com/elixxir/crypto v0.0.7-0.20210412231025-6f75c577f803 - gitlab.com/elixxir/ekv v0.1.4 - gitlab.com/elixxir/primitives v0.0.3-0.20210409190923-7bf3cd8d97e7 - gitlab.com/xx_network/comms v0.0.4-0.20210414225551-37262e764468 - gitlab.com/xx_network/crypto v0.0.5-0.20210413200952-56bd15ec9d99 - gitlab.com/xx_network/primitives v0.0.4-0.20210412170941-7ef69bce5a5c - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad - golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect + gitlab.com/elixxir/comms v0.0.4-0.20210429182303-0edbda5e1b2c + gitlab.com/elixxir/crypto v0.0.7-0.20210429182057-898c75770293 + gitlab.com/elixxir/ekv v0.1.5 + gitlab.com/elixxir/primitives v0.0.3-0.20210429180244-cdbb97da0c16 + gitlab.com/xx_network/comms v0.0.4-0.20210426213447-82674e09e402 + gitlab.com/xx_network/crypto v0.0.5-0.20210420170153-2a6276844076 + gitlab.com/xx_network/primitives v0.0.4-0.20210402222416-37c1c4d3fac4 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect + golang.org/x/text v0.3.6 // indirect google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect google.golang.org/grpc v1.34.0 // indirect google.golang.org/protobuf v1.26.0-rc.1 diff --git a/go.sum b/go.sum index 3e66ea636bbb55882fbbcca02e6b7f6f838bff2b..9c4b366bf5651eff852a775187540585485b0aca 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.schwanenlied.me/yawning/aez.git v0.0.0-20180408160647-ec7426b44926/go.mod h1:sXNmB4ljkeRBT0xvmbMGP7ldvC2C2CIUXBlqIO8XGc4= -git.schwanenlied.me/yawning/bsaes.git v0.0.0-20190320102049-26d1add596b6/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -21,7 +19,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-lambda-go v1.8.1/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A= github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ= github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -30,7 +27,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3/go.mod h1:l2CvGr3DNS9Egif8pwQqJ45Ci9Y/PPs0XJHTcRKbGBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -46,12 +42,10 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -136,17 +130,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/katzenpost/chacha20 v0.0.0-20190907175341-fad9676fa4d8/go.mod h1:d9kxwmGOcutgP6bQwr2xaLInaW5yJsxsoPRyUIG0J/E= -github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556/go.mod h1:d9kxwmGOcutgP6bQwr2xaLInaW5yJsxsoPRyUIG0J/E= -github.com/katzenpost/core v0.0.13/go.mod h1:RjyD4DLQr+P5FnP6iiZUTgvalZwT7NyawXwYzn32eYQ= -github.com/katzenpost/core v0.0.14 h1:8n30pGlm6cXa1PjU+HBv8nARFh67UmX/eYkHnbcBfgs= -github.com/katzenpost/core v0.0.14/go.mod h1:RjyD4DLQr+P5FnP6iiZUTgvalZwT7NyawXwYzn32eYQ= -github.com/katzenpost/newhope v0.0.0-20190907181500-0c77ddcb510f/go.mod h1:xA8+73aJ0TU2W701F6Ac7LL8jiUuv6me5pO+/nIuaCM= -github.com/katzenpost/noise v0.0.2/go.mod h1:L6ioEZo4vpnAgdh4x8qenV7T0/k8mltat1EjxQO0TNA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -159,8 +145,8 @@ github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea h1:uyJ13zfy6l79CM3Hn github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea/go.mod h1:w4pGU9PkiX2hAWyF0yuHEHmYTQFAd6WHzp6+IY7JVjE= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= -github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -175,8 +161,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= -github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -187,13 +173,12 @@ github.com/nyaruka/phonenumbers v1.0.60/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJr github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0= +github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -223,14 +208,13 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= -github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -252,9 +236,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -269,84 +250,44 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0= github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/comms v0.0.4-0.20210402183711-0350710740e7 h1:JfR2UVZDqAIQeicHq7ZIPuJyPzkcr59OT5uGz4XyFiI= -gitlab.com/elixxir/comms v0.0.4-0.20210402183711-0350710740e7/go.mod h1:3ikLStVfz4wUMgzL/WkDE9umkeizAKMlO5GQcnDmH5c= -gitlab.com/elixxir/comms v0.0.4-0.20210402205438-eca60a717c47 h1:w0SarO3yraN81dZJan7lDz6OjexoqP52kRAdlnfCr4o= -gitlab.com/elixxir/comms v0.0.4-0.20210402205438-eca60a717c47/go.mod h1:Hd9NbUwdRXFcs5ZgukPYsChmM8AqxJRjzMNvQuUXqh4= -gitlab.com/elixxir/comms v0.0.4-0.20210402205854-0ea056917a98 h1:6GF+txzdbf5EKZKXcwfHagH7G+vmDmMjQZAoMSsHXm4= -gitlab.com/elixxir/comms v0.0.4-0.20210402205854-0ea056917a98/go.mod h1:Hd9NbUwdRXFcs5ZgukPYsChmM8AqxJRjzMNvQuUXqh4= -gitlab.com/elixxir/comms v0.0.4-0.20210402222700-7fac5f85c596 h1:jX6H4vu//StDLKZn5lcmxp9S3IhzS/8Uttg0K5r7Iqo= -gitlab.com/elixxir/comms v0.0.4-0.20210402222700-7fac5f85c596/go.mod h1:jqqUYnsftpfQXJ57BPYp5A+i7qfA5IXhKUE9ZOSrqaE= -gitlab.com/elixxir/comms v0.0.4-0.20210405170210-0aa75aa7fefd h1:0wkkIeKyZ9SW+0MpxpZnypTOpePze8/r4USw1hooAJs= -gitlab.com/elixxir/comms v0.0.4-0.20210405170210-0aa75aa7fefd/go.mod h1:jqqUYnsftpfQXJ57BPYp5A+i7qfA5IXhKUE9ZOSrqaE= -gitlab.com/elixxir/comms v0.0.4-0.20210405183148-930ea17a1b5f h1:ZUY46FcnA7BOd9pCggproSRBqycV0X+zKyai3eTL2ys= -gitlab.com/elixxir/comms v0.0.4-0.20210405183148-930ea17a1b5f/go.mod h1:jqqUYnsftpfQXJ57BPYp5A+i7qfA5IXhKUE9ZOSrqaE= -gitlab.com/elixxir/comms v0.0.4-0.20210413194022-f5422be88efb h1:mPwoG9nAhihK4Rfl6Hph/DzMmkePv4zJ4nzTC+ewnEc= -gitlab.com/elixxir/comms v0.0.4-0.20210413194022-f5422be88efb/go.mod h1:2meX6DKpVJAUcgt1d727t1I28C8qsZNSn9Vm8dlkgmA= -gitlab.com/elixxir/comms v0.0.4-0.20210414195240-0a29afe5c282 h1:sv1Yiv18o8Q/tpBSP3Bpm2GrbQYdWcGaXELIU6OGxVc= -gitlab.com/elixxir/comms v0.0.4-0.20210414195240-0a29afe5c282/go.mod h1:K0sExgT6vTjMY/agP7hRSutveJzGSNOuFjSsAl2vmz8= -gitlab.com/elixxir/comms v0.0.4-0.20210414200820-10e888270d4d h1:bHhOyckCTs0mRdgasEWgyxSz3JgK1J0ZBaV5cp2IjLk= -gitlab.com/elixxir/comms v0.0.4-0.20210414200820-10e888270d4d/go.mod h1:K0sExgT6vTjMY/agP7hRSutveJzGSNOuFjSsAl2vmz8= -gitlab.com/elixxir/comms v0.0.4-0.20210414225623-fb1a8a82f210 h1:6r9esD2qZUXqll0I3KW50RQQiuN66jpDFAeqRPy1ZVs= -gitlab.com/elixxir/comms v0.0.4-0.20210414225623-fb1a8a82f210/go.mod h1:k93pPqBAdnHvoRlIpj6KGqx6HpJGbKMUI+v+PuBcCsM= +gitlab.com/elixxir/comms v0.0.4-0.20210427005410-7ae183abda2a h1:u3bGNs0IRcFw+OtRNBR87UCRWHLA57TkSF80HdReg4U= +gitlab.com/elixxir/comms v0.0.4-0.20210427005410-7ae183abda2a/go.mod h1:r9zz08vO5MjzAsfA6yd978PIkm0dVYAo4EooXc/ES1M= +gitlab.com/elixxir/comms v0.0.4-0.20210429182303-0edbda5e1b2c h1:+6hshgCT43Z4f+nNQUeIZGdIYhch5dGgJLcMzyUPujo= +gitlab.com/elixxir/comms v0.0.4-0.20210429182303-0edbda5e1b2c/go.mod h1:eRkJ3+6afFAqTyHneUN04ix4MIV16F52/GMHaNbGXZU= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4 h1:28ftZDeYEko7xptCZzeFWS1Iam95dj46TWFVVlKmw6A= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3 h1:znCt/x2bL4y8czTPaaFkwzdgSgW3BJc/1+dxyf1jqVw= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.6 h1:c94CGzBTV7LgInGHfmeJHrqq9nIc/WEOLUd9OeQBN74= -gitlab.com/elixxir/crypto v0.0.6/go.mod h1:V8lricBRpa8v1ySymXQ1/lsb+8/lSak5S7ZWRT6OACY= -gitlab.com/elixxir/crypto v0.0.7-0.20210401210040-b7f1da24ef13 h1:x6oSLhgzhBcXeItHQ7OlDNoyvebgyNdGCaywAP/IkMc= -gitlab.com/elixxir/crypto v0.0.7-0.20210401210040-b7f1da24ef13/go.mod h1:5k+LGynIQa42jZ/UbNVwBiZGHKvLXbXXkqyuTY6uHs0= -gitlab.com/elixxir/crypto v0.0.7-0.20210412231025-6f75c577f803 h1:8sLODlAYRT0Y9NA+uoMoF1qBrBRrW5TikyKAOvyCd+E= -gitlab.com/elixxir/crypto v0.0.7-0.20210412231025-6f75c577f803/go.mod h1:HMMRBuv/yMqB5c31G9OPlOAifOOqGypCyD5v6py+4vo= -gitlab.com/elixxir/ekv v0.1.4 h1:NLVMwsFEKArWcsDHu2DbXlm9374iSgn7oIA3rVSsvjc= -gitlab.com/elixxir/ekv v0.1.4/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= +gitlab.com/elixxir/crypto v0.0.7-0.20210427005255-4fe1bcf69c5a h1:DX+PiQ/koxI/uNNqAxUiFJXP+nbl5qN1u3lBElpauzQ= +gitlab.com/elixxir/crypto v0.0.7-0.20210427005255-4fe1bcf69c5a/go.mod h1:XuDhkhjI/h1w8OQW8neyP9hvt2KMH/GSCjRQTWg2AEk= +gitlab.com/elixxir/crypto v0.0.7-0.20210429182057-898c75770293 h1:7f7rYmDI1HvSgT0+1xhSb7rM5zxQjG005byDtr4BqX0= +gitlab.com/elixxir/crypto v0.0.7-0.20210429182057-898c75770293/go.mod h1:e2tQyVZR4Tbj4ashHIDF2k55Gj2MT+J6qhZxrszPA74= +gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk= +gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8= gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.1 h1:q61anawANlNAExfkeQEE1NCsNih6vNV1FFLoUQX6txQ= gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.3-0.20201116174806-97f190989704/go.mod h1:3fxFHSlQhkV4vs+S0dZEz3Om3m+40WX8L806yvSnNFc= -gitlab.com/elixxir/primitives v0.0.3-0.20210401175645-9b7b92f74ec4 h1:PFrOIpax1IMXS7jVGFhOF3bSOWh3IWhNUD18n1DzSZM= -gitlab.com/elixxir/primitives v0.0.3-0.20210401175645-9b7b92f74ec4/go.mod h1:9qqDucNbLP9ArL1VKCXQuqYrcAbJIUcI8uzbP7NmKDw= -gitlab.com/elixxir/primitives v0.0.3-0.20210409190923-7bf3cd8d97e7 h1:q3cw7WVtD6hDqTi8ydky+yiqJ4RkWp/hkTSNirr9Z6Y= -gitlab.com/elixxir/primitives v0.0.3-0.20210409190923-7bf3cd8d97e7/go.mod h1:h0QHrjrixLNaP24ZXAgDOZXP4eegrQ24BCZPGitg8Jg= +gitlab.com/elixxir/primitives v0.0.3-0.20210427004615-c68ecf15fcf3 h1:fQZzwSDzFymbXlTPVCDTrnBkOhFAx4fACwZQHpmTVOY= +gitlab.com/elixxir/primitives v0.0.3-0.20210427004615-c68ecf15fcf3/go.mod h1:h0QHrjrixLNaP24ZXAgDOZXP4eegrQ24BCZPGitg8Jg= +gitlab.com/elixxir/primitives v0.0.3-0.20210429180244-cdbb97da0c16 h1:ZIXWpEbSWUrVvqtrmo5/MVvDJm94eX5j2dgtGptkt6U= +gitlab.com/elixxir/primitives v0.0.3-0.20210429180244-cdbb97da0c16/go.mod h1:h0QHrjrixLNaP24ZXAgDOZXP4eegrQ24BCZPGitg8Jg= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.4-0.20210401160731-7b8890cdd8ad h1:0E4wnLoOqODo6K2SwVG18y63sns4WLN3x+nSM9SWfiM= -gitlab.com/xx_network/comms v0.0.4-0.20210401160731-7b8890cdd8ad/go.mod h1:inre/Ot0UJkxcfF4Oy4jk2A1MXyicRkPZB9FfnCfKQY= -gitlab.com/xx_network/comms v0.0.4-0.20210407173545-dafd47029306/go.mod h1:PXbUpBMUcDygpEV1ptEc/pych07YkYQ/tv0AQPw+BRk= -gitlab.com/xx_network/comms v0.0.4-0.20210413200413-41a493c32b06 h1:ejoRHig0h5oEXLbEqQbE3wjcJtEoK0q7mHkfZjphtRo= -gitlab.com/xx_network/comms v0.0.4-0.20210413200413-41a493c32b06/go.mod h1:PXbUpBMUcDygpEV1ptEc/pych07YkYQ/tv0AQPw+BRk= -gitlab.com/xx_network/comms v0.0.4-0.20210414191603-0904bc6eeda2 h1:4Xhw5zO9ggeD66z5SajzRcgkQ0RNdAENETwPlawo5aU= -gitlab.com/xx_network/comms v0.0.4-0.20210414191603-0904bc6eeda2/go.mod h1:PXbUpBMUcDygpEV1ptEc/pych07YkYQ/tv0AQPw+BRk= -gitlab.com/xx_network/comms v0.0.4-0.20210414225551-37262e764468 h1:09cm7A2rSbNZQdYH+95zPj5vu3FyM1Vy2H1REU3YgpA= -gitlab.com/xx_network/comms v0.0.4-0.20210414225551-37262e764468/go.mod h1:fUw26IhQ2MtjUnQVLO7TQ4tdr+g6qJPGrKfhXCb4R1E= +gitlab.com/xx_network/comms v0.0.4-0.20210426213447-82674e09e402 h1:owWfI0mh31/nUV8Dh70z+inpFhU4GItlpsjsqYnWn7c= +gitlab.com/xx_network/comms v0.0.4-0.20210426213447-82674e09e402/go.mod h1:PIgq/b4ucczEqCWAmPEnht/4QJw57+mPSICHiyMEstU= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4 h1:lpKOL5mTJ2awWMfgBy30oD/UvJVrWZzUimSHlOdZZxo= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20201124194022-366c10b1bce0/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20210401160648-4f06cace9123 h1:i2PajAamYlacUpAFWqE7g5qtM6Vt/xG9iDfoK1nc2l4= -gitlab.com/xx_network/crypto v0.0.5-0.20210401160648-4f06cace9123/go.mod h1:CWV349I9Nv1zPCIY/f8Ej6yWs7NG0HLTWnm+Jlz7jKc= -gitlab.com/xx_network/crypto v0.0.5-0.20210405224157-2b1f387b42c1/go.mod h1:CUhRpioyLaKIylg+LIyZX1rhOmFaEXQQ6esNycx9dcA= -gitlab.com/xx_network/crypto v0.0.5-0.20210413184628-dbc1dd97ed5e/go.mod h1:Rz97srKNbUOnMk+gKyRnJYIVIA6bGBj+BB5Of6Pfyng= -gitlab.com/xx_network/crypto v0.0.5-0.20210413200952-56bd15ec9d99 h1:2WYJ+gkFAzlcdvmBzmNxx5ANNEf+p08EoT+Qeiptzw8= -gitlab.com/xx_network/crypto v0.0.5-0.20210413200952-56bd15ec9d99/go.mod h1:Rz97srKNbUOnMk+gKyRnJYIVIA6bGBj+BB5Of6Pfyng= +gitlab.com/xx_network/crypto v0.0.5-0.20210420170153-2a6276844076 h1:bLR43541fUUhPTMGuCWKz7IEQevAm4OeTBVtsDt8tgg= +gitlab.com/xx_network/crypto v0.0.5-0.20210420170153-2a6276844076/go.mod h1:CUhRpioyLaKIylg+LIyZX1rhOmFaEXQQ6esNycx9dcA= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da h1:CCVslUwNC7Ul7NG5nu3ThGTSVUt1TxNRX+47f5TUwnk= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2 h1:r45yKenJ9e7PylI1ZXJ1Es09oYNaYXjxVy9+uYlwo7Y= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= -gitlab.com/xx_network/primitives v0.0.4-0.20210331161816-ed23858bdb93 h1:ZV5ZfSBX7+7moL8pywpCB3HTNXd03tSwMgDA7SDYaFA= -gitlab.com/xx_network/primitives v0.0.4-0.20210331161816-ed23858bdb93/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= -gitlab.com/xx_network/primitives v0.0.4-0.20210402183235-e04f174cf8c4 h1:uPTABEykN9moPQjb427tqVFPcxWbYbiqLZ26iwI8Cws= -gitlab.com/xx_network/primitives v0.0.4-0.20210402183235-e04f174cf8c4/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= -gitlab.com/xx_network/primitives v0.0.4-0.20210402205313-e9b80f75e701 h1:svcqDo2heNLjmUncmgymwRH3lkV5Jy3PdhvLuqmI39o= -gitlab.com/xx_network/primitives v0.0.4-0.20210402205313-e9b80f75e701/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/primitives v0.0.4-0.20210402222416-37c1c4d3fac4 h1:YPYTKF0zQf08y0eQrjQP01C/EWQTypdqawjZPr5c6rc= gitlab.com/xx_network/primitives v0.0.4-0.20210402222416-37c1c4d3fac4/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= -gitlab.com/xx_network/primitives v0.0.4-0.20210406200245-e5a84aeae64d/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= -gitlab.com/xx_network/primitives v0.0.4-0.20210412170941-7ef69bce5a5c h1:WpAkvPhg5NYkLkJ8M0/jD4VMuNpqQTahlwkVbbV8SHk= -gitlab.com/xx_network/primitives v0.0.4-0.20210412170941-7ef69bce5a5c/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0= gitlab.com/xx_network/ring v0.0.2/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -360,8 +301,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -371,9 +310,10 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -407,8 +347,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -428,7 +368,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -439,9 +378,10 @@ golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA= golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -451,6 +391,8 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -520,13 +462,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/interfaces/networkManager.go b/interfaces/networkManager.go index da548c855e48a30a5fff0895842f04975e00c88b..1daa7d38104731870459b1323c8d918401a398fd 100644 --- a/interfaces/networkManager.go +++ b/interfaces/networkManager.go @@ -10,6 +10,7 @@ package interfaces import ( "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/e2e" @@ -24,6 +25,7 @@ type NetworkManager interface { SendCMIX(message format.Message, recipient *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) GetInstance() *network.Instance GetHealthTracker() HealthTracker + GetSender() *gateway.Sender Follow(report ClientErrorReport) (stoppable.Stoppable, error) CheckGarbledMessages() InProgressRegistrations() int diff --git a/keyExchange/utils_test.go b/keyExchange/utils_test.go index f02e056657b6d3e4fdfcd102cfc3093ddabb45e9..a1d9d83f5550c47876a312c236f031afdbdd65ad 100644 --- a/keyExchange/utils_test.go +++ b/keyExchange/utils_test.go @@ -13,6 +13,7 @@ import ( "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/e2e" @@ -103,7 +104,11 @@ func (t *testNetworkManagerGeneric) InProgressRegistrations() int { return 0 } -func InitTestingContextGeneric(i interface{}) (*storage.Session, interfaces.NetworkManager, error) { +func (t *testNetworkManagerGeneric) GetSender() *gateway.Sender { + return nil +} + +func InitTestingContextGeneric(i interface{}) (*storage.Session, interfaces.NetworkManager) { switch i.(type) { case *testing.T, *testing.M, *testing.B, *testing.PB: break @@ -120,12 +125,12 @@ func InitTestingContextGeneric(i interface{}) (*storage.Session, interfaces.Netw thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, i) if err != nil { - return nil, nil, err + return nil, nil } thisManager := &testNetworkManagerGeneric{instance: thisInstance} - return thisSession, thisManager, nil + return thisSession, thisManager } @@ -210,6 +215,10 @@ func (t *testNetworkManagerFullExchange) InProgressRegistrations() int { return 0 } +func (t *testNetworkManagerFullExchange) GetSender() *gateway.Sender { + return nil +} + func InitTestingContextFullExchange(i interface{}) (*storage.Session, *switchboard.Switchboard, interfaces.NetworkManager) { switch i.(type) { case *testing.T, *testing.M, *testing.B, *testing.PB: diff --git a/network/checkedRounds.go b/network/checkedRounds.go index d104ac9589bfb9d7b7bce41d967b7013fdfb9141..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/network/checkedRounds.go +++ b/network/checkedRounds.go @@ -1,74 +0,0 @@ -package network - -import ( - "container/list" - "crypto/md5" - "gitlab.com/elixxir/client/storage/reception" - "gitlab.com/xx_network/primitives/id" -) - -type idFingerprint [16]byte - -type checkedRounds struct { - lookup map[idFingerprint]*checklist -} - -type checklist struct { - m map[id.Round]interface{} - l *list.List -} - -func newCheckedRounds() *checkedRounds { - return &checkedRounds{ - lookup: make(map[idFingerprint]*checklist), - } -} - -func (cr *checkedRounds) Check(identity reception.IdentityUse, rid id.Round) bool { - idFp := getIdFingerprint(identity) - cl, exists := cr.lookup[idFp] - if !exists { - cl = &checklist{ - m: make(map[id.Round]interface{}), - l: list.New().Init(), - } - cr.lookup[idFp] = cl - } - - if _, exists := cl.m[rid]; !exists { - cl.m[rid] = nil - cl.l.PushBack(rid) - return true - } - return false -} - -func (cr *checkedRounds) Prune(identity reception.IdentityUse, earliestAllowed id.Round) { - idFp := getIdFingerprint(identity) - cl, exists := cr.lookup[idFp] - if !exists { - return - } - - e := cl.l.Front() - for e != nil { - if e.Value.(id.Round) < earliestAllowed { - delete(cl.m, e.Value.(id.Round)) - lastE := e - e = e.Next() - cl.l.Remove(lastE) - } else { - break - } - } -} - -func getIdFingerprint(identity reception.IdentityUse) idFingerprint { - h := md5.New() - h.Write(identity.EphId[:]) - h.Write(identity.Source[:]) - - fp := idFingerprint{} - copy(fp[:], h.Sum(nil)) - return fp -} diff --git a/network/ephemeral/testutil.go b/network/ephemeral/testutil.go index fbcc4e3e8d031097f4d9447c0904f537451b3d40..d2708b0c71c7d112151e4494d3ef631a78adaf62 100644 --- a/network/ephemeral/testutil.go +++ b/network/ephemeral/testutil.go @@ -8,6 +8,7 @@ package ephemeral import ( + "gitlab.com/elixxir/client/network/gateway" "testing" jww "github.com/spf13/jwalterweatherman" @@ -79,6 +80,10 @@ func (t *testNetworkManager) InProgressRegistrations() int { return 0 } +func (t *testNetworkManager) GetSender() *gateway.Sender { + return nil +} + func NewTestNetworkManager(i interface{}) interfaces.NetworkManager { switch i.(type) { case *testing.T, *testing.M, *testing.B: diff --git a/network/follow.go b/network/follow.go index ebf06e5db5060b5fe69b8e032329ef660a79fdcb..dc9f0196a0e201c2c6e5fc3696ad537c776f5f12 100644 --- a/network/follow.go +++ b/network/follow.go @@ -23,18 +23,14 @@ package network // instance import ( - "bytes" "fmt" - "math" + "sync/atomic" "time" - jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/rounds" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/knownRounds" - "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" @@ -64,8 +60,9 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport, quitCh <-ch case <-ticker.C: m.follow(report, rng, m.Comms) case <-TrackTicker.C: - jww.INFO.Println(m.tracker.Report()) - m.tracker = newPollTracker() + numPolls := atomic.SwapUint64(m.tracker, 0) + jww.INFO.Printf("Polled the network %d times in the "+ + "last %s", numPolls, debugTrackPeriod) } } } @@ -80,15 +77,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, "impossible: %+v", err) } - m.tracker.Track(identity.EphId, identity.Source) - - //randomly select a gateway to poll - //TODO: make this more intelligent - gwHost, err := gateway.Get(m.Instance.GetPartialNdf().Get(), comms, rng) - if err != nil { - jww.FATAL.Panicf("Failed to follow network, NDF has corrupt "+ - "data: %s", err) - } + atomic.AddUint64(m.tracker, 1) // Get client version for poll version := m.Session.GetClientVersion() @@ -105,20 +94,28 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, ClientVersion: []byte(version.String()), FastPolling: m.param.FastPolling, } - jww.TRACE.Printf("Executing poll for %v(%s) range: %s-%s(%s) from %s", - identity.EphId.Int64(), identity.Source, identity.StartRequest, - identity.EndRequest, identity.EndRequest.Sub(identity.StartRequest), gwHost.GetId()) - pollResp, err := comms.SendPoll(gwHost, &pollReq) + result, err := m.GetSender().SendToAny(func(host *connect.Host) (interface{}, error) { + jww.DEBUG.Printf("Executing poll for %v(%s) range: %s-%s(%s) from %s", + identity.EphId.Int64(), identity.Source, identity.StartRequest, + identity.EndRequest, identity.EndRequest.Sub(identity.StartRequest), host.GetId()) + result, err := comms.SendPoll(host, &pollReq) + if err != nil { + if report != nil { + report( + "NetworkFollower", + fmt.Sprintf("Failed to poll network, \"%s\", Gateway: %s", err.Error(), host.String()), + fmt.Sprintf("%+v", err), + ) + } + jww.ERROR.Printf("Unable to poll %s for NDF: %+v", host, err) + } + return result, err + }) if err != nil { - report( - "NetworkFollower", - fmt.Sprintf("Failed to poll network, \"%s\", Gateway: %s", err.Error(), gwHost.String()), - fmt.Sprintf("%+v", err), - ) - jww.ERROR.Printf("Unable to poll %s for NDF: %+v", gwHost, err) return } + pollResp := result.(*pb.GatewayPollResponse) // ---- Process Network State Update Data ---- gwRoundsState := &knownRounds.KnownRounds{} @@ -138,11 +135,8 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, return } - err = m.Instance.UpdateGatewayConnections() - if err != nil { - jww.ERROR.Printf("Unable to update gateway connections: %+v", err) - return - } + // update gateway connections + m.GetSender().UpdateNdf(m.GetInstance().GetPartialNdf().Get()) } //check that the stored address space is correct @@ -158,51 +152,52 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, return } + // TODO: ClientErr needs to know the source of the error and it doesn't yet // Iterate over ClientErrors for each RoundUpdate - for _, update := range pollResp.Updates { - - // Ignore irrelevant updates - if update.State != uint32(states.COMPLETED) && update.State != uint32(states.FAILED) { - continue - } - - for _, clientErr := range update.ClientErrors { - - // If this Client appears in the ClientError - if bytes.Equal(clientErr.ClientId, m.Session.GetUser().TransmissionID.Marshal()) { - - // Obtain relevant NodeGateway information - nGw, err := m.Instance.GetNodeAndGateway(gwHost.GetId()) - if err != nil { - jww.ERROR.Printf("Unable to get NodeGateway: %+v", err) - return - } - nid, err := nGw.Node.GetNodeId() - if err != nil { - jww.ERROR.Printf("Unable to get NodeID: %+v", err) - return - } - - // FIXME: Should be able to trigger proper type of round event - // FIXME: without mutating the RoundInfo. Signature also needs verified - // FIXME: before keys are deleted - update.State = uint32(states.FAILED) - rnd, err := m.Instance.GetWrappedRound(id.Round(update.ID)) - if err != nil { - jww.ERROR.Printf("Failed to report client error: "+ - "Could not get round for event triggering: "+ - "Unable to get round %d from instance: %+v", - id.Round(update.ID), err) - break - } - m.Instance.GetRoundEvents().TriggerRoundEvent(rnd) - - // delete all existing keys and trigger a re-registration with the relevant Node - m.Session.Cmix().Remove(nid) - m.Instance.GetAddGatewayChan() <- nGw - } - } - } + //for _, update := range pollResp.Updates { + // + // // Ignore irrelevant updates + // if update.State != uint32(states.COMPLETED) && update.State != uint32(states.FAILED) { + // continue + // } + // + // for _, clientErr := range update.ClientErrors { + // // If this Client appears in the ClientError + // if bytes.Equal(clientErr.ClientId, m.Session.GetUser().TransmissionID.Marshal()) { + // + // // Obtain relevant NodeGateway information + // // TODO ??? + // nGw, err := m.Instance.GetNodeAndGateway(gwHost.GetId()) + // if err != nil { + // jww.ERROR.Printf("Unable to get NodeGateway: %+v", err) + // return + // } + // nid, err := nGw.Node.GetNodeId() + // if err != nil { + // jww.ERROR.Printf("Unable to get NodeID: %+v", err) + // return + // } + // + // // FIXME: Should be able to trigger proper type of round event + // // FIXME: without mutating the RoundInfo. Signature also needs verified + // // FIXME: before keys are deleted + // update.State = uint32(states.FAILED) + // rnd, err := m.Instance.GetWrappedRound(id.Round(update.ID)) + // if err != nil { + // jww.ERROR.Printf("Failed to report client error: "+ + // "Could not get round for event triggering: "+ + // "Unable to get round %d from instance: %+v", + // id.Round(update.ID), err) + // break + // } + // m.Instance.GetRoundEvents().TriggerRoundEvent(rnd) + // + // // delete all existing keys and trigger a re-registration with the relevant Node + // m.Session.Cmix().Remove(nid) + // m.Instance.GetAddGatewayChan() <- nGw + // } + // } + //} } // ---- Identity Specific Round Processing ----- @@ -225,20 +220,12 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, return } - firstRound := id.Round(math.MaxUint64) - lastRound := id.Round(0) //prepare the filter objects for processing - filterList := make([]*rounds.RemoteFilter, filtersEnd-filtersStart) + filterList := make([]*rounds.RemoteFilter, 0, filtersEnd-filtersStart) for i := filtersStart; i < filtersEnd; i++ { if len(pollResp.Filters.Filters[i].Filter) != 0 { - filterList[i-filtersStart] = rounds.NewRemoteFilter(pollResp.Filters.Filters[i]) - if filterList[i-filtersStart].FirstRound() < firstRound { - firstRound = filterList[i-filtersStart].FirstRound() - } - if filterList[i-filtersStart].LastRound() > lastRound { - lastRound = filterList[i-filtersStart].LastRound() - } + filterList= append(filterList,rounds.NewRemoteFilter(pollResp.Filters.Filters[i])) } } @@ -246,7 +233,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, // are messages waiting in rounds and then sends signals to the appropriate // handling threads roundChecker := func(rid id.Round) bool { - return rounds.Checker(rid, filterList) + return rounds.Checker(rid, filterList, identity.CR) } // move the earliest unknown round tracker forward to the earliest @@ -268,30 +255,25 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, roundsWithMessages2 := identity.UR.Iterate(func(rid id.Round) bool { if gwRoundsState.Checked(rid) { - return rounds.Checker(rid, filterList) + return rounds.Checker(rid, filterList, identity.CR) } return false }, roundsUnknown) for _, rid := range roundsWithMessages { - if m.checked.Check(identity, rid) { + if identity.CR.Check(rid) { m.round.GetMessagesFromRound(rid, identity) } } - for _, rid := range roundsWithMessages2 { - m.round.GetMessagesFromRound(rid, identity) - } - earliestToKeep := getEarliestToKeep(m.param.KnownRoundsThreshold, - gwRoundsState.GetLastChecked()) - - m.checked.Prune(identity, earliestToKeep) - -} + identity.CR.Prune() + err = identity.CR.SaveCheckedRounds() + if err != nil { + jww.ERROR.Printf("Could not save rounds for identity %d (%s): %+v", + identity.EphId.Int64(), identity.Source, err) + } -func getEarliestToKeep(delta uint, lastchecked id.Round) id.Round { - if uint(lastchecked) < delta { - return 0 + for _, rid := range roundsWithMessages2 { + m.round.GetMessagesFromRound(rid, identity) } - return lastchecked - id.Round(delta) } diff --git a/network/gateway/gateway.go b/network/gateway/gateway.go deleted file mode 100644 index f4a16cf2e78a5be6fe9cadbcd496b2cd7bb95b79..0000000000000000000000000000000000000000 --- a/network/gateway/gateway.go +++ /dev/null @@ -1,112 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package gateway - -import ( - "encoding/binary" - "fmt" - "github.com/pkg/errors" - "gitlab.com/elixxir/comms/mixmessages" - "gitlab.com/elixxir/crypto/shuffle" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/ndf" - "io" - "math" -) - -type HostGetter interface { - GetHost(hostId *id.ID) (*connect.Host, bool) -} - -// Get the Host of a random gateway in the NDF -func Get(ndf *ndf.NetworkDefinition, hg HostGetter, rng io.Reader) (*connect.Host, error) { - gwLen := uint32(len(ndf.Gateways)) - if gwLen == 0 { - return nil, errors.Errorf("no gateways available") - } - - gwIdx := ReadRangeUint32(0, gwLen, rng) - gwID, err := id.Unmarshal(ndf.Nodes[gwIdx].ID) - if err != nil { - return nil, errors.WithMessage(err, "failed to get Gateway") - } - gwID.SetType(id.Gateway) - gwHost, ok := hg.GetHost(gwID) - if !ok { - return nil, errors.Errorf("host for gateway %s could not be "+ - "retrieved", gwID) - } - return gwHost, nil -} - -// GetAllShuffled returns a shufled list of gateway hosts from the specified round -func GetAllShuffled(hg HostGetter, ri *mixmessages.RoundInfo) ([]*connect.Host, error) { - roundTop := ri.GetTopology() - hosts := make([]*connect.Host, 0) - shuffledList := make([]uint64, 0) - - // Collect all host information from the round - for index, _ := range roundTop { - selectedId, err := id.Unmarshal(roundTop[index]) - if err != nil { - return nil, err - } - - selectedId.SetType(id.Gateway) - - gwHost, ok := hg.GetHost(selectedId) - if !ok { - return nil, errors.Errorf("Could not find host for gateway %s", selectedId) - } - hosts = append(hosts, gwHost) - shuffledList = append(shuffledList, uint64(index)) - } - - returnHosts := make([]*connect.Host, len(hosts)) - - // Shuffle a list corresponding to the valid gateway hosts - shuffle.Shuffle(&shuffledList) - - // Index through the shuffled list, building a list - // of shuffled gateways from the round - for index, shuffledIndex := range shuffledList { - returnHosts[index] = hosts[shuffledIndex] - } - - return returnHosts, nil - -} - -// ReadUint32 reads an integer from an io.Reader (which should be a CSPRNG) -func ReadUint32(rng io.Reader) uint32 { - var rndBytes [4]byte - i, err := rng.Read(rndBytes[:]) - if i != 4 || err != nil { - panic(fmt.Sprintf("cannot read from rng: %+v", err)) - } - return binary.BigEndian.Uint32(rndBytes[:]) -} - -// ReadRangeUint32 reduces an integer from 0, MaxUint32 to the range start, end -func ReadRangeUint32(start, end uint32, rng io.Reader) uint32 { - size := end - start - // note we could just do the part inside the () here, but then extra - // can == size which means a little bit of range is wastes, either - // choice seems negligible so we went with the "more correct" - extra := (math.MaxUint32%size + 1) % size - limit := math.MaxUint32 - extra - // Loop until we read something inside the limit - for { - res := ReadUint32(rng) - if res > limit { - continue - } - return (res % size) + start - } -} diff --git a/network/gateway/hostPool.go b/network/gateway/hostPool.go new file mode 100644 index 0000000000000000000000000000000000000000..f165ce5a9424a37e08e2442bdafc884959a2472b --- /dev/null +++ b/network/gateway/hostPool.go @@ -0,0 +1,465 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +// Package gateway Handles functionality related to providing Gateway connect.Host objects +// for message sending to the rest of the client repo +// Used to minimize # of open connections on mobile clients + +package gateway + +import ( + "encoding/binary" + "fmt" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" + "io" + "math" + "strings" + "sync" + "time" +) + +// List of errors that initiate a Host replacement +var errorsList = []string{"context deadline exceeded", "connection refused", "host disconnected", + "transport is closing", "all SubConns are in TransientFailure", "Last try to connect", + ndf.NO_NDF, "Host is in cool down"} + +// HostManager Interface allowing storage and retrieval of Host objects +type HostManager interface { + GetHost(hostId *id.ID) (*connect.Host, bool) + AddHost(hid *id.ID, address string, cert []byte, params connect.HostParams) (host *connect.Host, err error) + RemoveHost(hid *id.ID) +} + +// HostPool Handles providing hosts to the Client +type HostPool struct { + hostMap map[id.ID]uint32 // map key to its index in the slice + hostList []*connect.Host // each index in the slice contains the value + hostMux sync.RWMutex // Mutex for the above map/list combination + + ndfMap map[id.ID]int // map gateway ID to its index in the ndf + ndf *ndf.NetworkDefinition + ndfMux sync.RWMutex + + poolParams PoolParams + rng *fastRNG.StreamGenerator + storage *storage.Session + manager HostManager + addGatewayChan chan network.NodeGateway +} + +// PoolParams Allows configuration of HostPool parameters +type PoolParams struct { + MaxPoolSize uint32 // Maximum number of Hosts in the HostPool + PoolSize uint32 // Allows override of HostPool size. Set to zero for dynamic size calculation + // TODO: Move up a layer + ProxyAttempts uint32 // How many proxies will be used in event of send failure + HostParams connect.HostParams // Parameters for the creation of new Host objects +} + +// DefaultPoolParams Returns a default set of PoolParams +func DefaultPoolParams() PoolParams { + p := PoolParams{ + MaxPoolSize: 30, + ProxyAttempts: 5, + PoolSize: 0, + HostParams: connect.GetDefaultHostParams(), + } + p.HostParams.MaxRetries = 1 + p.HostParams.AuthEnabled = false + p.HostParams.EnableCoolOff = true + p.HostParams.NumSendsBeforeCoolOff = 1 + p.HostParams.CoolOffTimeout = 5 * time.Minute + return p +} + +// Build and return new HostPool object +func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.NetworkDefinition, getter HostManager, + storage *storage.Session, addGateway chan network.NodeGateway) (*HostPool, error) { + var err error + + // Determine size of HostPool + if poolParams.PoolSize == 0 { + poolParams.PoolSize, err = getPoolSize(uint32(len(ndf.Gateways)), poolParams.MaxPoolSize) + if err != nil { + return nil, err + } + } + + result := &HostPool{ + manager: getter, + hostMap: make(map[id.ID]uint32), + hostList: make([]*connect.Host, poolParams.PoolSize), + poolParams: poolParams, + ndf: ndf, + rng: rng, + storage: storage, + addGatewayChan: addGateway, + } + + // Propagate the NDF + err = result.updateConns() + if err != nil { + return nil, err + } + + // Build the initial HostPool and return + for i := 0; i < len(result.hostList); i++ { + err := result.forceReplace(uint32(i)) + if err != nil { + return nil, err + } + } + + jww.INFO.Printf("Initialized HostPool with size: %d/%d", poolParams.PoolSize, len(ndf.Gateways)) + return result, nil +} + +// UpdateNdf Mutates internal ndf to the given ndf +func (h *HostPool) UpdateNdf(ndf *ndf.NetworkDefinition) { + if len(ndf.Gateways) == 0 { + jww.WARN.Printf("Unable to UpdateNdf: no gateways available") + return + } + + h.ndfMux.Lock() + h.ndf = ndf + + h.hostMux.Lock() + err := h.updateConns() + h.hostMux.Unlock() + if err != nil { + jww.ERROR.Printf("Unable to updateConns: %+v", err) + } + h.ndfMux.Unlock() +} + +// Obtain a random, unique list of Hosts of the given length from the HostPool +func (h *HostPool) getAny(length uint32, excluded []*id.ID) []*connect.Host { + if length > h.poolParams.PoolSize { + length = h.poolParams.PoolSize + } + + checked := make(map[uint32]interface{}) // Keep track of Hosts already selected to avoid duplicates + if excluded != nil { + // Add excluded Hosts to already-checked list + for i := range excluded { + gwId := excluded[i] + if idx, ok := h.hostMap[*gwId]; ok { + checked[idx] = nil + } + } + } + + result := make([]*connect.Host, 0, length) + rng := h.rng.GetStream() + h.hostMux.RLock() + for i := uint32(0); i < length; { + // If we've checked the entire HostPool, bail + if uint32(len(checked)) >= h.poolParams.PoolSize { + break + } + + // Check the next HostPool index + gwIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + if _, ok := checked[gwIdx]; !ok { + result = append(result, h.hostList[gwIdx]) + checked[gwIdx] = nil + i++ + } + } + h.hostMux.RUnlock() + rng.Close() + + return result +} + +// Obtain a specific connect.Host from the manager, irrespective of the HostPool +func (h *HostPool) getSpecific(target *id.ID) (*connect.Host, bool) { + return h.manager.GetHost(target) +} + +// Try to obtain the given targets from the HostPool +// If each is not present, obtain a random replacement from the HostPool +func (h *HostPool) getPreferred(targets []*id.ID) []*connect.Host { + checked := make(map[uint32]interface{}) // Keep track of Hosts already selected to avoid duplicates + length := len(targets) + if length > int(h.poolParams.PoolSize) { + length = int(h.poolParams.PoolSize) + } + result := make([]*connect.Host, length) + + rng := h.rng.GetStream() + h.hostMux.RLock() + for i := 0; i < length; { + if hostIdx, ok := h.hostMap[*targets[i]]; ok { + result[i] = h.hostList[hostIdx] + checked[hostIdx] = nil + i++ + continue + } + + gwIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + if _, ok := checked[gwIdx]; !ok { + result[i] = h.hostList[gwIdx] + checked[gwIdx] = nil + i++ + } + } + h.hostMux.RUnlock() + rng.Close() + + return result +} + +// Replaces the given hostId in the HostPool if the given hostErr is in errorList +func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) error { + // Check if Host should be replaced + doReplace := false + if hostErr != nil { + for _, errString := range errorsList { + if strings.Contains(hostErr.Error(), errString) { + // Host needs replaced, flag and continue + doReplace = true + break + } + } + } + + if doReplace { + h.hostMux.Lock() + defer h.hostMux.Unlock() + + // If the Host is still in the pool + if oldPoolIndex, ok := h.hostMap[*hostId]; ok { + // Replace it + h.ndfMux.RLock() + err := h.forceReplace(oldPoolIndex) + h.ndfMux.RUnlock() + return err + } + } + return nil +} + +// Replace given Host index with a new, randomly-selected Host from the NDF +func (h *HostPool) forceReplace(oldPoolIndex uint32) error { + rng := h.rng.GetStream() + defer rng.Close() + + // Loop until a replacement Host is found + for { + // Randomly select a new Gw by index in the NDF + ndfIdx := readRangeUint32(0, uint32(len(h.ndf.Gateways)), rng) + jww.DEBUG.Printf("Attempting to replace Host at HostPool %d with Host at NDF %d...", oldPoolIndex, ndfIdx) + + // Use the random ndfIdx to obtain a GwId from the NDF + gwId, err := id.Unmarshal(h.ndf.Gateways[ndfIdx].ID) + if err != nil { + return errors.WithMessage(err, "failed to get Gateway for pruning") + } + + // Verify the new GwId is not already in the hostMap + if _, ok := h.hostMap[*gwId]; !ok { + // If it is a new GwId, replace the old Host with the new Host + return h.replaceHost(gwId, oldPoolIndex) + } + } +} + +// Replace the given slot in the HostPool with a new Gateway with the specified ID +func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { + // Obtain that GwId's Host object + newHost, ok := h.manager.GetHost(newId) + if !ok { + return errors.Errorf("host for gateway %s could not be "+ + "retrieved", newId) + } + + // Keep track of oldHost for cleanup + oldHost := h.hostList[oldPoolIndex] + + // Use the poolIdx to overwrite the random Host in the corresponding index in the hostList + h.hostList[oldPoolIndex] = newHost + // Use the GwId to keep track of the new random Host's index in the hostList + h.hostMap[*newId] = oldPoolIndex + + // Clean up and move onto next Host + if oldHost != nil { + delete(h.hostMap, *oldHost.GetId()) + go oldHost.Disconnect() + } + jww.DEBUG.Printf("Replaced Host at %d with new Host %s", oldPoolIndex, newId.String()) + return nil +} + +// Force-add the Gateways to the HostPool, each replacing a random Gateway +func (h *HostPool) forceAdd(gwId *id.ID) error { + rng := h.rng.GetStream() + h.hostMux.Lock() + defer h.hostMux.Unlock() + defer rng.Close() + + // Verify the GwId is not already in the hostMap + if _, ok := h.hostMap[*gwId]; ok { + // If it is, skip + return nil + } + + // Randomly select another Gateway in the HostPool for replacement + poolIdx := readRangeUint32(0, h.poolParams.PoolSize, rng) + return h.replaceHost(gwId, poolIdx) +} + +// Updates the internal HostPool with any changes to the NDF +func (h *HostPool) updateConns() error { + // Prepare NDFs for comparison + newMap, err := convertNdfToMap(h.ndf) + if err != nil { + return errors.Errorf("Unable to convert new NDF to set: %+v", err) + } + + // Handle adding Gateways + for gwId, ndfIdx := range newMap { + if _, ok := h.ndfMap[gwId]; !ok { + // If GwId in newMap is not in ndfMap, add the Gateway + h.addGateway(gwId.DeepCopy(), ndfIdx) + } + } + + // Handle removing Gateways + for gwId := range h.ndfMap { + if _, ok := newMap[gwId]; !ok { + // If GwId in ndfMap is not in newMap, remove the Gateway + h.removeGateway(gwId.DeepCopy()) + } + } + + // Update the internal NDF set + h.ndfMap = newMap + return nil +} + +// Takes ndf.Gateways and puts their IDs into a map object +func convertNdfToMap(ndf *ndf.NetworkDefinition) (map[id.ID]int, error) { + result := make(map[id.ID]int) + if ndf == nil { + return result, nil + } + + // Process gateway Id's into set + for i := range ndf.Gateways { + gw := ndf.Gateways[i] + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + return nil, err + } + result[*gwId] = i + } + + return result, nil +} + +// updateConns helper for removing old Gateways +func (h *HostPool) removeGateway(gwId *id.ID) { + h.manager.RemoveHost(gwId) + // If needed, replace the removed Gateway in the HostPool with a new one + if poolIndex, ok := h.hostMap[*gwId]; ok { + err := h.forceReplace(poolIndex) + if err != nil { + jww.ERROR.Printf("Unable to removeGateway: %+v", err) + } + } +} + +// updateConns helper for adding new Gateways +func (h *HostPool) addGateway(gwId *id.ID, ndfIndex int) { + gw := h.ndf.Gateways[ndfIndex] + + //check if the host exists + host, ok := h.manager.GetHost(gwId) + if !ok { + + // Check if gateway ID collides with an existing hard coded ID + if id.CollidesWithHardCodedID(gwId) { + jww.ERROR.Printf("Gateway ID invalid, collides with a "+ + "hard coded ID. Invalid ID: %v", gwId.Marshal()) + } + + // Add the new gateway host + _, err := h.manager.AddHost(gwId, gw.Address, []byte(gw.TlsCertificate), h.poolParams.HostParams) + if err != nil { + jww.ERROR.Printf("Could not add gateway host %s: %+v", gwId, err) + } + + // Send AddGateway event if we do not already possess keys for the GW + if !h.storage.Cmix().Has(gwId) { + ng := network.NodeGateway{ + Node: h.ndf.Nodes[ndfIndex], + Gateway: gw, + } + + select { + case h.addGatewayChan <- ng: + default: + jww.WARN.Printf("Unable to send AddGateway event for id %s", gwId.String()) + } + } + + } else if host.GetAddress() != gw.Address { + host.UpdateAddress(gw.Address) + } +} + +// getPoolSize determines the size of the HostPool based on the size of the NDF +func getPoolSize(ndfLen, maxSize uint32) (uint32, error) { + // Verify the NDF has at least one Gateway for the HostPool + if ndfLen == 0 { + return 0, errors.Errorf("Unable to create HostPool: no gateways available") + } + + // PoolSize = ceil(sqrt(len(ndf,Gateways))) + poolSize := uint32(math.Ceil(math.Sqrt(float64(ndfLen)))) + if poolSize > maxSize { + return maxSize, nil + } + return poolSize, nil +} + +// readUint32 reads an integer from an io.Reader (which should be a CSPRNG) +func readUint32(rng io.Reader) uint32 { + var rndBytes [4]byte + i, err := rng.Read(rndBytes[:]) + if i != 4 || err != nil { + panic(fmt.Sprintf("cannot read from rng: %+v", err)) + } + return binary.BigEndian.Uint32(rndBytes[:]) +} + +// readRangeUint32 reduces an integer from 0, MaxUint32 to the range start, end +func readRangeUint32(start, end uint32, rng io.Reader) uint32 { + size := end - start + // note we could just do the part inside the () here, but then extra + // can == size which means a little bit of range is wastes, either + // choice seems negligible so we went with the "more correct" + extra := (math.MaxUint32%size + 1) % size + limit := math.MaxUint32 - extra + // Loop until we read something inside the limit + for { + res := readUint32(rng) + if res > limit { + continue + } + return (res % size) + start + } +} diff --git a/network/gateway/hostpool_test.go b/network/gateway/hostpool_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8181e6d49f0ea5522e8ae33eb6ac143516c45a26 --- /dev/null +++ b/network/gateway/hostpool_test.go @@ -0,0 +1,832 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package gateway + +import ( + "fmt" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" + "reflect" + "testing" +) + +// Unit test +func TestNewHostPool(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, "", nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + t.FailNow() + } + + } + + // Call the constructor + _, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } +} + +// Unit test +func TestHostPool_ManageHostPool(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + + // Construct custom params + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + t.FailNow() + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Construct a list of new gateways/nodes to add to ndf + newGatewayLen := len(testNdf.Gateways) + newGateways := make([]ndf.Gateway, newGatewayLen) + newNodes := make([]ndf.Node, newGatewayLen) + for i := 0; i < newGatewayLen; i++ { + // Construct gateways + gwId := id.NewIdFromUInt(uint64(100+i), id.Gateway, t) + newGateways[i] = ndf.Gateway{ID: gwId.Bytes()} + // Construct nodes + nodeId := gwId.DeepCopy() + nodeId.SetType(id.Node) + newNodes[i] = ndf.Node{ID: nodeId.Bytes()} + + } + + newNdf := getTestNdf(t) + // Update the ndf, removing some gateways at a cutoff + newNdf.Gateways = newGateways + newNdf.Nodes = newNodes + + testPool.UpdateNdf(newNdf) + + // Check that old gateways are not in pool + for _, ndfGw := range testNdf.Gateways { + gwId, err := id.Unmarshal(ndfGw.ID) + if err != nil { + t.Errorf("Failed to marshal gateway id for %v", ndfGw) + } + if _, ok := testPool.hostMap[*gwId]; ok { + t.Errorf("Expected gateway %v to be removed from pool", gwId) + } + } +} + +// Full happy path test +func TestHostPool_ReplaceHost(t *testing.T) { + manager := newMockManager() + testNdf := getTestNdf(t) + newIndex := uint32(20) + + // Construct a manager (bypass business logic in constructor) + hostPool := &HostPool{ + manager: manager, + hostList: make([]*connect.Host, newIndex+1), + hostMap: make(map[id.ID]uint32), + ndf: testNdf, + } + + /* "Replace" a host with no entry */ + + // Pull a gateway ID from the ndf + gwIdOne, err := id.Unmarshal(testNdf.Gateways[0].ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + + // Add mock gateway to manager + _, err = manager.AddHost(gwIdOne, "", nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + } + + // "Replace" (insert) the host + err = hostPool.replaceHost(gwIdOne, newIndex) + if err != nil { + t.Errorf("Could not replace host: %v", err) + } + + // Check the state of the map has been correctly updated + retrievedIndex, ok := hostPool.hostMap[*gwIdOne] + if !ok { + t.Errorf("Expected insertion of gateway ID into map") + } + if retrievedIndex != newIndex { + t.Errorf("Index pulled from map not expected value."+ + "\n\tExpected: %d"+ + "\n\tReceived: %d", newIndex, retrievedIndex) + } + + // Check that the state of the list list been correctly updated + retrievedHost := hostPool.hostList[newIndex] + if !gwIdOne.Cmp(retrievedHost.GetId()) { + t.Errorf("Id pulled from list is not expected."+ + "\n\tExpected: %s"+ + "\n\tReceived: %s", gwIdOne, retrievedHost.GetId()) + } + + /* Replace the initial host with a new host */ + + // Pull a different gateway ID from the ndf + gwIdTwo, err := id.Unmarshal(testNdf.Gateways[1].ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + + // Add second mock gateway to manager + _, err = manager.AddHost(gwIdTwo, "", nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + } + + // Replace the old host + err = hostPool.replaceHost(gwIdTwo, newIndex) + if err != nil { + t.Errorf("Could not replace host: %v", err) + } + + // Check that the state of the list been correctly updated for new host + retrievedHost = hostPool.hostList[newIndex] + if !gwIdTwo.Cmp(retrievedHost.GetId()) { + t.Errorf("Id pulled from list is not expected."+ + "\n\tExpected: %s"+ + "\n\tReceived: %s", gwIdTwo, retrievedHost.GetId()) + } + + // Check the state of the map has been correctly removed for the old gateway + retrievedOldIndex, ok := hostPool.hostMap[*gwIdOne] + if ok { + t.Errorf("Exoected old gateway to be cleared from map") + } + if retrievedOldIndex != 0 { + t.Errorf("Index pulled from map with old gateway as the key "+ + "was not cleared."+ + "\n\tExpected: %d"+ + "\n\tReceived: %d", 0, retrievedOldIndex) + } + + // Check the state of the map has been correctly updated for the old gateway + retrievedIndex, ok = hostPool.hostMap[*gwIdTwo] + if !ok { + t.Errorf("Expected insertion of gateway ID into map") + } + if retrievedIndex != newIndex { + t.Errorf("Index pulled from map using new gateway as the key "+ + "was not updated."+ + "\n\tExpected: %d"+ + "\n\tReceived: %d", newIndex, retrievedIndex) + } + +} + +// Error path, could not get host +func TestHostPool_ReplaceHost_Error(t *testing.T) { + manager := newMockManager() + + // Construct a manager (bypass business logic in constructor) + hostPool := &HostPool{ + manager: manager, + hostList: make([]*connect.Host, 1), + hostMap: make(map[id.ID]uint32), + } + + // Construct an unknown gateway ID to the manager + gatewayId := id.NewIdFromString("BadGateway", id.Gateway, t) + + err := hostPool.replaceHost(gatewayId, 0) + if err == nil { + t.Errorf("Expected error in happy path: Should not be able to find a host") + } + +} + +// Unit test +func TestHostPool_ForceReplace(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + + // Construct custom params + params := DefaultPoolParams() + params.PoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + t.FailNow() + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Add all gateways to hostPool's map + for index, gw := range testNdf.Gateways { + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + + err = testPool.replaceHost(gwId, uint32(index)) + if err != nil { + t.Fatalf("Failed to replace host in set-up: %v", err) + } + } + + oldGatewayIndex := 0 + oldHost := testPool.hostList[oldGatewayIndex] + + // Force replace the gateway at a given index + err = testPool.forceReplace(uint32(oldGatewayIndex)) + if err != nil { + t.Errorf("Failed to force replace: %v", err) + } + + // Ensure that old gateway has been removed from the map + if _, ok := testPool.hostMap[*oldHost.GetId()]; ok { + t.Errorf("Expected old host to be removed from map") + } + + // Ensure we are disconnected from the old host + if isConnected, _ := oldHost.Connected(); isConnected { + t.Errorf("Failed to disconnect from old host %s", oldHost) + } + +} + +// Unit test +func TestHostPool_CheckReplace(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + + // Construct custom params + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) - 5 + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + t.FailNow() + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Call check replace + oldGatewayIndex := 0 + oldHost := testPool.hostList[oldGatewayIndex] + expectedError := fmt.Errorf(errorsList[0]) + err = testPool.checkReplace(oldHost.GetId(), expectedError) + if err != nil { + t.Errorf("Failed to check replace: %v", err) + } + + // Ensure that old gateway has been removed from the map + if _, ok := testPool.hostMap[*oldHost.GetId()]; ok { + t.Errorf("Expected old host to be removed from map") + } + + // Ensure we are disconnected from the old host + if isConnected, _ := oldHost.Connected(); isConnected { + t.Errorf("Failed to disconnect from old host %s", oldHost) + } + + // Check that an error not in the global list results in a no-op + goodGatewayIndex := 0 + goodGateway := testPool.hostList[goodGatewayIndex] + unexpectedErr := fmt.Errorf("not in global error list") + err = testPool.checkReplace(oldHost.GetId(), unexpectedErr) + if err != nil { + t.Errorf("Failed to check replace: %v", err) + } + + // Ensure that gateway with an unexpected error was not modified + if _, ok := testPool.hostMap[*goodGateway.GetId()]; !ok { + t.Errorf("Expected gateway with non-expected error to not be modified") + } + + // Ensure gateway host has not been disconnected + if isConnected, _ := oldHost.Connected(); isConnected { + t.Errorf("Should not disconnect from %s", oldHost) + } + +} + +// Unit test +func TestHostPool_UpdateNdf(t *testing.T) { + manager := newMockManager() + testNdf := getTestNdf(t) + newIndex := uint32(20) + + // Construct a manager (bypass business logic in constructor) + hostPool := &HostPool{ + manager: manager, + hostList: make([]*connect.Host, newIndex+1), + hostMap: make(map[id.ID]uint32), + ndf: testNdf, + storage: storage.InitTestingSession(t), + } + + // Construct a new Ndf different from original one above + newNdf := getTestNdf(t) + newGateway := ndf.Gateway{ + ID: id.NewIdFromUInt(27, id.Gateway, t).Bytes(), + } + newNode := ndf.Node{ + ID: id.NewIdFromUInt(27, id.Node, t).Bytes(), + } + newNdf.Gateways = append(newNdf.Gateways, newGateway) + newNdf.Nodes = append(newNdf.Nodes, newNode) + + // Update pool with the new Ndf + hostPool.UpdateNdf(newNdf) + + // Check that the host pool's ndf has been modified properly + if !reflect.DeepEqual(newNdf, hostPool.ndf) { + t.Errorf("Host pool ndf not updated to new ndf.") + } +} + +// Full test +func TestHostPool_GetPreferred(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.PoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + hostMap := make(map[id.ID]bool, 0) + targets := make([]*id.ID, 0) + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + hostMap[*gwId] = true + targets = append(targets, gwId) + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + retrievedList := testPool.getPreferred(targets) + if len(retrievedList) != len(targets) { + t.Errorf("Requested list did not output requested length."+ + "\n\tExpected: %d"+ + "\n\tReceived: %v", len(targets), len(retrievedList)) + } + + // In case where all requested gateways are present + // ensure requested hosts were returned + for _, h := range retrievedList { + if !hostMap[*h.GetId()] { + t.Errorf("A target gateways which should have been returned was not."+ + "\n\tExpected: %v", h.GetId()) + } + } + + // Replace a request with a gateway not in pool + targets[3] = id.NewIdFromUInt(74, id.Gateway, t) + retrievedList = testPool.getPreferred(targets) + if len(retrievedList) != len(targets) { + t.Errorf("Requested list did not output requested length."+ + "\n\tExpected: %d"+ + "\n\tReceived: %v", len(targets), len(retrievedList)) + } + + // In case where a requested gateway is not present + for _, h := range retrievedList { + if h.GetId().Cmp(targets[3]) { + t.Errorf("Should not have returned ID not in pool") + } + } + +} + +// Unit test +func TestHostPool_GetAny(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + requested := 3 + anyList := testPool.getAny(uint32(requested), nil) + if len(anyList) != requested { + t.Errorf("GetAnyList did not get requested length."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", requested, len(anyList)) + } + + for _, h := range anyList { + _, ok := manager.GetHost(h.GetId()) + if !ok { + t.Errorf("Host %s in retrieved list not in manager", h) + } + } + + // Request more than are in host list + largeRequest := uint32(requested * 1000) + largeRetrieved := testPool.getAny(largeRequest, nil) + if len(largeRetrieved) != len(testPool.hostList) { + t.Errorf("Large request should result in a list of all in host list") + } + +} + +// Unit test +func TestHostPool_ForceAdd(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.PoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Construct a new gateway to add + gwId := id.NewIdFromUInt(uint64(100), id.Gateway, t) + // Add mock gateway to manager + _, err = manager.AddHost(gwId, "", nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + // forceAdd gateway + err = testPool.forceAdd(gwId) + if err != nil { + t.Errorf("Could not add gateways: %v", err) + } + + // check that gateways have been added to the map + if _, ok := testPool.hostMap[*gwId]; !ok { + t.Errorf("Failed to forcefully add new gateway ID: %v", gwId) + } +} + +// Unit test which only adds information to ndf +func TestHostPool_UpdateConns_AddGateways(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Construct a list of new gateways/nodes to add to ndf + newGatewayLen := 10 + newGateways := make([]ndf.Gateway, newGatewayLen) + newNodes := make([]ndf.Node, newGatewayLen) + for i := 0; i < newGatewayLen; i++ { + // Construct gateways + gwId := id.NewIdFromUInt(uint64(100+i), id.Gateway, t) + newGateways[i] = ndf.Gateway{ID: gwId.Bytes()} + // Construct nodes + nodeId := gwId.DeepCopy() + nodeId.SetType(id.Node) + newNodes[i] = ndf.Node{ID: nodeId.Bytes()} + + } + + // Update the ndf + newNdf := getTestNdf(t) + newNdf.Gateways = append(newNdf.Gateways, newGateways...) + newNdf.Nodes = append(newNdf.Nodes, newNodes...) + + testPool.UpdateNdf(newNdf) + + // Update the connections + err = testPool.updateConns() + if err != nil { + t.Errorf("Failed to update connections: %v", err) + } + + // Check that new gateways are in manager + for _, ndfGw := range newGateways { + gwId, err := id.Unmarshal(ndfGw.ID) + if err != nil { + t.Errorf("Failed to marshal gateway id for %v", ndfGw) + } + _, ok := testPool.getSpecific(gwId) + if !ok { + t.Errorf("Failed to find gateway %v in manager", gwId) + } + } + +} + +// Unit test which only adds information to ndf +func TestHostPool_UpdateConns_RemoveGateways(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Errorf("Could not add mock host to manager: %v", err) + t.FailNow() + } + + } + + // Call the constructor + testPool, err := newHostPool(params, rng, testNdf, manager, + testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Construct a list of new gateways/nodes to add to ndf + newGatewayLen := len(testNdf.Gateways) + newGateways := make([]ndf.Gateway, newGatewayLen) + newNodes := make([]ndf.Node, newGatewayLen) + for i := 0; i < newGatewayLen; i++ { + // Construct gateways + gwId := id.NewIdFromUInt(uint64(100+i), id.Gateway, t) + newGateways[i] = ndf.Gateway{ID: gwId.Bytes()} + // Construct nodes + nodeId := gwId.DeepCopy() + nodeId.SetType(id.Node) + newNodes[i] = ndf.Node{ID: nodeId.Bytes()} + + } + + // Update the ndf, replacing old data entirely + newNdf := getTestNdf(t) + newNdf.Gateways = newGateways + newNdf.Nodes = newNodes + + testPool.UpdateNdf(newNdf) + + // Update the connections + err = testPool.updateConns() + if err != nil { + t.Errorf("Failed to update connections: %v", err) + } + + // Check that old gateways are not in pool + for _, ndfGw := range testNdf.Gateways { + gwId, err := id.Unmarshal(ndfGw.ID) + if err != nil { + t.Errorf("Failed to marshal gateway id for %v", ndfGw) + } + if _, ok := testPool.hostMap[*gwId]; ok { + t.Errorf("Expected gateway %v to be removed from pool", gwId) + } + } +} + +// Unit test +func TestHostPool_AddGateway(t *testing.T) { + manager := newMockManager() + testNdf := getTestNdf(t) + newIndex := uint32(20) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Construct a manager (bypass business logic in constructor) + hostPool := &HostPool{ + manager: manager, + hostList: make([]*connect.Host, newIndex+1), + hostMap: make(map[id.ID]uint32), + ndf: testNdf, + addGatewayChan: make(chan network.NodeGateway), + storage: storage.InitTestingSession(t), + } + + ndfIndex := 0 + + gwId, err := id.Unmarshal(testNdf.Gateways[ndfIndex].ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + + hostPool.addGateway(gwId, ndfIndex) + + _, ok := manager.GetHost(gwId) + if !ok { + t.Errorf("Unsuccessfully added host to manager") + } +} + +// Unit test +func TestHostPool_RemoveGateway(t *testing.T) { + manager := newMockManager() + testNdf := getTestNdf(t) + newIndex := uint32(20) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + // Construct a manager (bypass business logic in constructor) + hostPool := &HostPool{ + manager: manager, + hostList: make([]*connect.Host, newIndex+1), + hostMap: make(map[id.ID]uint32), + ndf: testNdf, + addGatewayChan: make(chan network.NodeGateway), + storage: storage.InitTestingSession(t), + rng: fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), + } + + ndfIndex := 0 + + gwId, err := id.Unmarshal(testNdf.Gateways[ndfIndex].ID) + if err != nil { + t.Errorf("Failed to unmarshal ID in mock ndf: %v", err) + } + + // Manually add host information + hostPool.addGateway(gwId, ndfIndex) + + // Call the removal + hostPool.removeGateway(gwId) + + // Check that the map and list have been updated + if hostPool.hostList[ndfIndex] != nil { + t.Errorf("Host list index was not set to nil after removal") + } + + if _, ok := hostPool.hostMap[*gwId]; ok { + t.Errorf("Host map did not delete host entry") + } +} diff --git a/network/gateway/sender.go b/network/gateway/sender.go new file mode 100644 index 0000000000000000000000000000000000000000..dcfcdbb5b88a0be02b67f4f61dfcaa01c1169489 --- /dev/null +++ b/network/gateway/sender.go @@ -0,0 +1,132 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2021 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +// Contains gateway message sending wrappers + +package gateway + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" +) + +// Sender Object used for sending that wraps the HostPool for providing destinations +type Sender struct { + *HostPool +} + +// NewSender Create a new Sender object wrapping a HostPool object +func NewSender(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.NetworkDefinition, getter HostManager, + storage *storage.Session, addGateway chan network.NodeGateway) (*Sender, error) { + + hostPool, err := newHostPool(poolParams, rng, ndf, getter, storage, addGateway) + if err != nil { + return nil, err + } + return &Sender{hostPool}, nil +} + +// SendToSpecific Call given sendFunc to a specific Host in the HostPool, +// attempting with up to numProxies destinations in case of failure +func (s *Sender) SendToSpecific(target *id.ID, + sendFunc func(host *connect.Host, target *id.ID) (interface{}, bool, error)) (interface{}, error) { + host, ok := s.getSpecific(target) + if ok { + result, didAbort, err := sendFunc(host, target) + if err == nil { + return result, s.forceAdd(target) + } else { + if didAbort { + return nil, errors.WithMessagef(err, "Aborted SendToSpecific gateway %s", host.GetId().String()) + } + jww.WARN.Printf("Unable to SendToSpecific %s: %s", host.GetId().String(), err) + } + } + + proxies := s.getAny(s.poolParams.ProxyAttempts, []*id.ID{target}) + for i := range proxies { + result, didAbort, err := sendFunc(proxies[i], target) + if err == nil { + return result, nil + } else { + if didAbort { + return nil, errors.WithMessagef(err, "Aborted SendToSpecific gateway proxy %s", + host.GetId().String()) + } + jww.WARN.Printf("Unable to SendToSpecific proxy %s: %s", proxies[i].GetId().String(), err) + err = s.checkReplace(proxies[i].GetId(), err) + if err != nil { + jww.ERROR.Printf("Unable to checkReplace: %+v", err) + } + } + } + + return nil, errors.Errorf("Unable to send to specific with proxies") +} + +// SendToAny Call given sendFunc to any Host in the HostPool, attempting with up to numProxies destinations +func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error)) (interface{}, error) { + + proxies := s.getAny(s.poolParams.ProxyAttempts, nil) + for i := range proxies { + result, err := sendFunc(proxies[i]) + if err == nil { + return result, nil + } else { + jww.WARN.Printf("Unable to SendToAny %s: %s", proxies[i].GetId().String(), err) + err = s.checkReplace(proxies[i].GetId(), err) + if err != nil { + jww.ERROR.Printf("Unable to checkReplace: %+v", err) + } + } + } + + return nil, errors.Errorf("Unable to send to any proxies") +} + +// SendToPreferred Call given sendFunc to any Host in the HostPool, attempting with up to numProxies destinations +func (s *Sender) SendToPreferred(targets []*id.ID, + sendFunc func(host *connect.Host, target *id.ID) (interface{}, error)) (interface{}, error) { + + targetHosts := s.getPreferred(targets) + for i := range targetHosts { + result, err := sendFunc(targetHosts[i], targets[i]) + if err == nil { + return result, nil + } else { + jww.WARN.Printf("Unable to SendToPreferred %s via %s: %s", + targets[i], targetHosts[i].GetId(), err) + err = s.checkReplace(targetHosts[i].GetId(), err) + if err != nil { + jww.ERROR.Printf("Unable to checkReplace: %+v", err) + } + } + } + + proxies := s.getAny(s.poolParams.ProxyAttempts, targets) + for i := range proxies { + target := targets[i%len(targets)].DeepCopy() + result, err := sendFunc(proxies[i], target) + if err == nil { + return result, nil + } else { + jww.WARN.Printf("Unable to SendToPreferred %s via proxy "+ + "%s: %s", target, proxies[i].GetId(), err) + err = s.checkReplace(proxies[i].GetId(), err) + if err != nil { + jww.ERROR.Printf("Unable to checkReplace: %+v", err) + } + } + } + + return nil, errors.Errorf("Unable to send to any preferred") +} diff --git a/network/gateway/sender_test.go b/network/gateway/sender_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4dd5d49c02ea525e547164f8c2c6392b526b30de --- /dev/null +++ b/network/gateway/sender_test.go @@ -0,0 +1,249 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package gateway + +import ( + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" +) + +// Unit test +func TestNewSender(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + _, err := NewSender(params, rng, testNdf, manager, testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) + } +} + +// Unit test +func TestSender_SendToAny(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.PoolSize = uint32(len(testNdf.Gateways)) + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + sender, err := NewSender(params, rng, testNdf, manager, testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) + } + + // Add all gateways to hostPool's map + for index, gw := range testNdf.Gateways { + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + + err = sender.replaceHost(gwId, uint32(index)) + if err != nil { + t.Fatalf("Failed to replace host in set-up: %v", err) + } + } + + // Test sendToAny with test interfaces + result, err := sender.SendToAny(SendToAny_HappyPath) + if err != nil { + t.Errorf("Should not error in SendToAny happy path: %v", err) + } + + if !reflect.DeepEqual(result, happyPathReturn) { + t.Errorf("Expected result not returnev via SendToAny interface."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", happyPathReturn, result) + } + + _, err = sender.SendToAny(SendToAny_KnownError) + if err == nil { + t.Fatalf("Expected error path did not receive error") + } + + _, err = sender.SendToAny(SendToAny_UnknownError) + if err == nil { + t.Fatalf("Expected error path did not receive error") + } + +} + +// Unit test +func TestSender_SendToPreferred(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.PoolSize = uint32(len(testNdf.Gateways)) - 5 + + // Do not test proxy attempts code in this test + // (self contain to code specific in sendPreferred) + params.ProxyAttempts = 0 + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + sender, err := NewSender(params, rng, testNdf, manager, testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) + } + + preferredIndex := 0 + preferredHost := sender.hostList[preferredIndex] + + // Happy path + result, err := sender.SendToPreferred([]*id.ID{preferredHost.GetId()}, SendToPreferred_HappyPath) + if err != nil { + t.Errorf("Should not error in SendToPreferred happy path: %v", err) + } + + if !reflect.DeepEqual(result, happyPathReturn) { + t.Errorf("Expected result not returnev via SendToPreferred interface."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", happyPathReturn, result) + } + + // Call a send which returns an error which triggers replacement + _, err = sender.SendToPreferred([]*id.ID{preferredHost.GetId()}, SendToPreferred_KnownError) + if err == nil { + t.Fatalf("Expected error path did not receive error") + } + + // Check the host has been replaced + if _, ok := sender.hostMap[*preferredHost.GetId()]; ok { + t.Errorf("Expected host %s to be removed due to error", preferredHost) + } + + // Ensure we are disconnected from the old host + if isConnected, _ := preferredHost.Connected(); isConnected { + t.Errorf("ForceReplace error: Failed to disconnect from old host %s", preferredHost) + } + + // Get a new host to test on + preferredIndex = 4 + preferredHost = sender.hostList[preferredIndex] + + // Unknown error return will not trigger replacement + _, err = sender.SendToPreferred([]*id.ID{preferredHost.GetId()}, SendToPreferred_UnknownError) + if err == nil { + t.Fatalf("Expected error path did not receive error") + } + + // Check the host has not been replaced + if _, ok := sender.hostMap[*preferredHost.GetId()]; !ok { + t.Errorf("Host %s should not have been removed due on an unknown error", preferredHost) + } + + // Ensure we are disconnected from the old host + if isConnected, _ := preferredHost.Connected(); isConnected { + t.Errorf("Should not disconnect from %s", preferredHost) + } + +} + +func TestSender_SendToSpecific(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) - 5 + + // Do not test proxy attempts code in this test + // (self contain to code specific in sendPreferred) + params.ProxyAttempts = 0 + + // Pull all gateways from ndf into host manager + for _, gw := range testNdf.Gateways { + + gwId, err := id.Unmarshal(gw.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) + } + // Add mock gateway to manager + _, err = manager.AddHost(gwId, gw.Address, nil, connect.GetDefaultHostParams()) + if err != nil { + t.Fatalf("Could not add mock host to manager: %v", err) + } + + } + + sender, err := NewSender(params, rng, testNdf, manager, testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) + } + + preferredIndex := 0 + preferredHost := sender.hostList[preferredIndex] + + // Happy path + result, err := sender.SendToSpecific(preferredHost.GetId(), SendToSpecific_HappyPath) + if err != nil { + t.Errorf("Should not error in SendToSpecific happy path: %v", err) + } + + if !reflect.DeepEqual(result, happyPathReturn) { + t.Errorf("Expected result not returnev via SendToSpecific interface."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", happyPathReturn, result) + } + + // Ensure host is now in map + if _, ok := sender.hostMap[*preferredHost.GetId()]; !ok { + t.Errorf("Failed to forcefully add new gateway ID: %v", preferredHost.GetId()) + } + + _, err = sender.SendToSpecific(preferredHost.GetId(), SendToSpecific_Abort) + if err == nil { + t.Errorf("Expected sendSpecific to return an abort") + } + +} diff --git a/network/gateway/utils_test.go b/network/gateway/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0ec7dc11f8edde72a82affd3b718bcec121bf60c --- /dev/null +++ b/network/gateway/utils_test.go @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package gateway + +import ( + "fmt" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" +) + +// Mock structure adhering to HostManager to be used for happy path +type mockManager struct { + hosts map[string]*connect.Host +} + +// Constructor for mockManager +func newMockManager() *mockManager { + return &mockManager{ + hosts: make(map[string]*connect.Host), + } +} + +func (mhp *mockManager) GetHost(hostId *id.ID) (*connect.Host, bool) { + h, ok := mhp.hosts[hostId.String()] + return h, ok +} + +func (mhp *mockManager) AddHost(hid *id.ID, address string, + cert []byte, params connect.HostParams) (host *connect.Host, err error) { + host, err = connect.NewHost(hid, address, cert, params) + if err != nil { + return nil, err + } + + mhp.hosts[hid.String()] = host + + return +} + +func (mhp *mockManager) RemoveHost(hid *id.ID) { + delete(mhp.hosts, hid.String()) +} + +// Returns a mock +func getTestNdf(face interface{}) *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + Gateways: []ndf.Gateway{{ + ID: id.NewIdFromUInt(0, id.Gateway, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(1, id.Gateway, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(2, id.Gateway, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(3, id.Gateway, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(4, id.Gateway, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(5, id.Gateway, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(6, id.Gateway, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(7, id.Gateway, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(8, id.Gateway, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(9, id.Gateway, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(10, id.Gateway, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(11, id.Gateway, face)[:], + Address: "0.0.0.3", + }}, + Nodes: []ndf.Node{{ + ID: id.NewIdFromUInt(0, id.Node, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(1, id.Node, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(2, id.Node, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(3, id.Node, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(4, id.Node, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(5, id.Node, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(6, id.Node, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(7, id.Node, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(8, id.Node, face)[:], + Address: "0.0.0.3", + }, { + ID: id.NewIdFromUInt(9, id.Node, face)[:], + Address: "0.0.0.1", + }, { + ID: id.NewIdFromUInt(10, id.Node, face)[:], + Address: "0.0.0.2", + }, { + ID: id.NewIdFromUInt(11, id.Node, face)[:], + Address: "0.0.0.3", + }}, + } +} + +const happyPathReturn = "happyPathReturn" + +func SendToPreferred_HappyPath(host *connect.Host, target *id.ID) (interface{}, error) { + return happyPathReturn, nil +} + +func SendToPreferred_KnownError(host *connect.Host, target *id.ID) (interface{}, error) { + return nil, fmt.Errorf(errorsList[0]) +} + +func SendToPreferred_UnknownError(host *connect.Host, target *id.ID) (interface{}, error) { + return nil, fmt.Errorf("Unexpected error: Oopsie") +} + +func SendToAny_HappyPath(host *connect.Host) (interface{}, error) { + return happyPathReturn, nil +} + +func SendToAny_KnownError(host *connect.Host) (interface{}, error) { + return nil, fmt.Errorf(errorsList[0]) +} + +func SendToAny_UnknownError(host *connect.Host) (interface{}, error) { + return nil, fmt.Errorf("Unexpected error: Oopsie") +} + +func SendToSpecific_HappyPath(host *connect.Host, target *id.ID) (interface{}, bool, error) { + return happyPathReturn, false, nil +} + +func SendToSpecific_Abort(host *connect.Host, target *id.ID) (interface{}, bool, error) { + return nil, true, fmt.Errorf(errorsList[0]) +} diff --git a/network/manager.go b/network/manager.go index 76fdeb0b5559fe0fc64bf6e8bf4b8f3aaa7eca29..e2b6c2c0c05d2d5ce48f846d09eb1f765e52dba5 100644 --- a/network/manager.go +++ b/network/manager.go @@ -15,6 +15,7 @@ import ( "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/network/ephemeral" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/health" "gitlab.com/elixxir/client/network/internal" "gitlab.com/elixxir/client/network/message" @@ -35,6 +36,8 @@ import ( type manager struct { // parameters of the network param params.Network + // handles message sending + sender *gateway.Sender //Shared data with all sub managers internal.Internal @@ -43,11 +46,8 @@ type manager struct { round *rounds.Manager message *message.Manager - //map of polls for debugging - tracker *pollTracker - - //tracks already checked rounds - checked *checkedRounds + //number of polls done in a period of time + tracker *uint64 } // NewManager builds a new reception manager object using inputted key fields @@ -67,11 +67,12 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, // set them here when they are needed on startup session.E2e().SetE2ESessionParams(params.E2EParams) + tracker := uint64(0) + //create manager object m := manager{ param: params, - tracker: newPollTracker(), - checked: newCheckedRounds(), + tracker: &tracker, } m.Internal = internal.Internal{ @@ -86,18 +87,22 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, ReceptionID: session.User().GetCryptographicIdentity().GetReceptionID(), } - // register the node registration channel early so login connection updates - // get triggered for registration if necessary - instance.SetAddGatewayChan(m.NodeRegistration) + // Set up gateway.Sender + poolParams := gateway.DefaultPoolParams() + m.sender, err = gateway.NewSender(poolParams, rng, + ndf, comms, session, m.NodeRegistration) + if err != nil { + return nil, err + } //create sub managers - m.message = message.NewManager(m.Internal, m.param.Messages, m.NodeRegistration) - m.round = rounds.NewManager(m.Internal, m.param.Rounds, m.message.GetMessageReceptionChannel()) + m.message = message.NewManager(m.Internal, m.param.Messages, m.NodeRegistration, m.sender) + m.round = rounds.NewManager(m.Internal, m.param.Rounds, m.message.GetMessageReceptionChannel(), m.sender) return &m, nil } -// StartRunners kicks off all network reception goroutines ("threads"). +// Follow StartRunners kicks off all network reception goroutines ("threads"). // Started Threads are: // - Network Follower (/network/follow.go) // - Historical Round Retrieval (/network/rounds/historical.go) @@ -118,7 +123,7 @@ func (m *manager) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppab multi.Add(healthStop) // Node Updates - multi.Add(node.StartRegistration(m.Instance, m.Session, m.Rng, + multi.Add(node.StartRegistration(m.GetSender(), m.Session, m.Rng, m.Comms, m.NodeRegistration, m.param.ParallelNodeRegistrations)) // Adding/Keys //TODO-remover //m.runners.Add(StartNodeRemover(m.Context)) // Removing @@ -149,7 +154,12 @@ func (m *manager) GetInstance() *network.Instance { return m.Instance } -// triggers a check on garbled messages to see if they can be decrypted +// GetSender returns the gateway.Sender object +func (m *manager) GetSender() *gateway.Sender { + return m.sender +} + +// CheckGarbledMessages triggers a check on garbled messages to see if they can be decrypted // this should be done when a new e2e client is added in case messages were // received early or arrived out of order func (m *manager) CheckGarbledMessages() { diff --git a/network/message/critical.go b/network/message/critical.go index 0ebaed5d32390c35f92a6a96557782be6336ff1e..1dad92c821e756e3c89c6d013c5d78c9e09a1015 100644 --- a/network/message/critical.go +++ b/network/message/critical.go @@ -95,7 +95,7 @@ func (m *Manager) criticalMessages() { jww.INFO.Printf("Resending critical raw message to %s "+ "(msgDigest: %s)", rid, msg.Digest()) //send the message - round, _, err := m.SendCMIX(msg, rid, param) + round, _, err := m.SendCMIX(m.sender, msg, rid, param) //if the message fail to send, notify the buffer so it can be handled //in the future and exit if err != nil { diff --git a/network/message/garbled_test.go b/network/message/garbled_test.go index 0f5c3a074641af02ef3e156372bd7957eedba5aa..9d021ae488ed26717aade2d76d87ba688c7f9001 100644 --- a/network/message/garbled_test.go +++ b/network/message/garbled_test.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/internal" "gitlab.com/elixxir/client/network/message/parse" "gitlab.com/elixxir/client/storage" @@ -57,12 +58,18 @@ func TestManager_CheckGarbledMessages(t *testing.T) { Instance: nil, NodeRegistration: nil, } + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + sender, err := gateway.NewSender(p, i.Rng, getNDF(), &MockSendCMIXComms{t: t}, i.Session, nil) + if err != nil { + t.Errorf(err.Error()) + } m := NewManager(i, params.Messages{ MessageReceptionBuffLen: 20, MessageReceptionWorkerPoolSize: 20, MaxChecksGarbledMessage: 20, GarbledMessageWait: time.Hour, - }, nil) + }, nil, sender) e2ekv := i.Session.E2e() err = e2ekv.AddPartner(sess2.GetUser().TransmissionID, sess2.E2e().GetDHPublicKey(), e2ekv.GetDHPrivateKey(), diff --git a/network/message/manager.go b/network/message/manager.go index 807ccc07066cf2281e557b6e07d09dd4180d3c15..7728910aa7e62186c682dcb97b297cb470dcac58 100644 --- a/network/message/manager.go +++ b/network/message/manager.go @@ -10,6 +10,7 @@ package message import ( "fmt" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/internal" "gitlab.com/elixxir/client/network/message/parse" "gitlab.com/elixxir/client/stoppable" @@ -21,6 +22,7 @@ type Manager struct { param params.Messages partitioner parse.Partitioner internal.Internal + sender *gateway.Sender messageReception chan Bundle nodeRegistration chan network.NodeGateway @@ -29,7 +31,7 @@ type Manager struct { } func NewManager(internal internal.Internal, param params.Messages, - nodeRegistration chan network.NodeGateway) *Manager { + nodeRegistration chan network.NodeGateway, sender *gateway.Sender) *Manager { dummyMessage := format.NewMessage(internal.Session.Cmix().GetGroup().GetP().ByteLen()) m := Manager{ param: param, @@ -38,6 +40,7 @@ func NewManager(internal internal.Internal, param params.Messages, networkIsHealthy: make(chan bool, 1), triggerGarbled: make(chan struct{}, 100), nodeRegistration: nodeRegistration, + sender: sender, } m.Internal = internal return &m diff --git a/network/message/sendCmix.go b/network/message/sendCmix.go index 1aced6efe44eaef52f17a08392d4c52a8d6eae7c..a3659870b8489cc3ab42e322ae5d6a79f73a54d0 100644 --- a/network/message/sendCmix.go +++ b/network/message/sendCmix.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/storage" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/network" @@ -29,7 +30,6 @@ import ( // interface for SendCMIX comms; allows mocking this in testing type sendCmixCommsInterface interface { - GetHost(hostId *id.ID) (*connect.Host, bool) SendPutMessage(host *connect.Host, message *pb.GatewaySlot) (*pb.GatewaySlotResponse, error) } @@ -38,9 +38,9 @@ const sendTimeBuffer = 2500 * time.Millisecond // WARNING: Potentially Unsafe // Public manager function to send a message over CMIX -func (m *Manager) SendCMIX(msg format.Message, recipient *id.ID, param params.CMIX) (id.Round, ephemeral.Id, error) { +func (m *Manager) SendCMIX(sender *gateway.Sender, msg format.Message, recipient *id.ID, param params.CMIX) (id.Round, ephemeral.Id, error) { msgCopy := msg.Copy() - return sendCmixHelper(msgCopy, recipient, param, m.Instance, m.Session, m.nodeRegistration, m.Rng, m.TransmissionID, m.Comms) + return sendCmixHelper(sender, msgCopy, recipient, param, m.Instance, m.Session, m.nodeRegistration, m.Rng, m.TransmissionID, m.Comms) } // Payloads send are not End to End encrypted, MetaData is NOT protected with @@ -51,7 +51,7 @@ func (m *Manager) SendCMIX(msg format.Message, recipient *id.ID, param params.CM // If the message is successfully sent, the id of the round sent it is returned, // which can be registered with the network instance to get a callback on // its status -func sendCmixHelper(msg format.Message, recipient *id.ID, param params.CMIX, instance *network.Instance, +func sendCmixHelper(sender *gateway.Sender, msg format.Message, recipient *id.ID, param params.CMIX, instance *network.Instance, session *storage.Session, nodeRegistration chan network.NodeGateway, rng *fastRNG.StreamGenerator, senderId *id.ID, comms sendCmixCommsInterface) (id.Round, ephemeral.Id, error) { @@ -142,15 +142,6 @@ func sendCmixHelper(msg format.Message, recipient *id.ID, param params.CMIX, ins firstGateway := topology.GetNodeAtIndex(0).DeepCopy() firstGateway.SetType(id.Gateway) - transmitGateway, ok := comms.GetHost(firstGateway) - if !ok { - jww.ERROR.Printf("Failed to get host for gateway %s when "+ - "sending to %s (msgDigest: %s)", transmitGateway, recipient, - msg.Digest()) - time.Sleep(param.RetryDelay) - continue - } - //encrypt the message stream = rng.GetStream() salt := make([]byte, 32) @@ -187,46 +178,57 @@ func sendCmixHelper(msg format.Message, recipient *id.ID, param params.CMIX, ins jww.INFO.Printf("Sending to EphID %d (%s) on round %d, "+ "(msgDigest: %s, ecrMsgDigest: %s) via gateway %s", ephID.Int64(), recipient, bestRound.ID, msg.Digest(), - encMsg.Digest(), transmitGateway.GetId()) - // //Send the payload - gwSlotResp, err := comms.SendPutMessage(transmitGateway, wrappedMsg) + encMsg.Digest(), firstGateway.String()) + + // Send the payload + result, err := sender.SendToSpecific(firstGateway, func(host *connect.Host, target *id.ID) (interface{}, bool, error) { + wrappedMsg.Target = target.Marshal() + result, err := comms.SendPutMessage(host, wrappedMsg) + if err != nil { + if strings.Contains(err.Error(), + "try a different round.") { + jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+ + "due to round error with round %d, retrying: %+v", + recipient, msg.Digest(), bestRound.ID, err) + return nil, true, err + } else if strings.Contains(err.Error(), + "Could not authenticate client. Is the client registered "+ + "with this node?") { + jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+ + "via %s due to failed authentication: %s", + recipient, msg.Digest(), firstGateway.String(), err) + //if we failed to send due to the gateway not recognizing our + // authorization, renegotiate with the node to refresh it + nodeID := firstGateway.DeepCopy() + nodeID.SetType(id.Node) + //delete the keys + session.Cmix().Remove(nodeID) + //trigger + go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID}) + return nil, true, err + } + } + return result, false, err + }) + //if the comm errors or the message fails to send, continue retrying. //return if it sends properly if err != nil { - if strings.Contains(err.Error(), - "try a different round.") { - jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+ - "due to round error with round %d, retrying: %+v", - recipient, msg.Digest(), bestRound.ID, err) - continue - } else if strings.Contains(err.Error(), - "Could not authenticate client. Is the client registered "+ - "with this node?") { - jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+ - "via %s due to failed authentication: %s", - recipient, msg.Digest(), transmitGateway.GetId(), err) - //if we failed to send due to the gateway not recognizing our - // authorization, renegotiate with the node to refresh it - nodeID := transmitGateway.GetId().DeepCopy() - nodeID.SetType(id.Node) - //delete the keys - session.Cmix().Remove(nodeID) - //trigger - go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID}) - continue - } jww.ERROR.Printf("Failed to send to EphID %d (%s) on "+ - "round %d, bailing: %+v", ephID.Int64(), recipient, + "round %d, trying a new round: %+v", ephID.Int64(), recipient, bestRound.ID, err) - return 0, ephemeral.Id{}, errors.WithMessage(err, "Failed to put cmix message") - } else if gwSlotResp.Accepted { + continue + } + + gwSlotResp := result.(*pb.GatewaySlotResponse) + if gwSlotResp.Accepted { jww.INFO.Printf("Successfully sent to EphID %v (source: %s) "+ "in round %d", ephID.Int64(), recipient, bestRound.ID) return id.Round(bestRound.ID), ephID, nil } else { jww.FATAL.Panicf("Gateway %s returned no error, but failed "+ "to accept message when sending to EphID %d (%s) on round %d", - transmitGateway.GetId(), ephID.Int64(), recipient, bestRound.ID) + firstGateway.String(), ephID.Int64(), recipient, bestRound.ID) } } return 0, ephemeral.Id{}, errors.New("failed to send the message, " + diff --git a/network/message/sendCmix_test.go b/network/message/sendCmix_test.go index 4b801679c0371c8ab4294a69c7e27883ef78801d..44a123b65d681ab6a46f9f696c2ef0e6ad08cc33 100644 --- a/network/message/sendCmix_test.go +++ b/network/message/sendCmix_test.go @@ -1,8 +1,10 @@ package message import ( + "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/internal" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/switchboard" @@ -40,6 +42,16 @@ func (mc *MockSendCMIXComms) GetHost(hostId *id.ID) (*connect.Host, bool) { }) return h, true } + +func (mc *MockSendCMIXComms) AddHost(hid *id.ID, address string, cert []byte, params connect.HostParams) (host *connect.Host, err error) { + host, _ = mc.GetHost(nil) + return host, nil +} + +func (mc *MockSendCMIXComms) RemoveHost(hid *id.ID) { + +} + func (mc *MockSendCMIXComms) SendPutMessage(host *connect.Host, message *mixmessages.GatewaySlot) (*mixmessages.GatewaySlotResponse, error) { return &mixmessages.GatewaySlotResponse{ Accepted: true, @@ -115,24 +127,36 @@ func Test_attemptSendCmix(t *testing.T) { Instance: inst, NodeRegistration: nil, } + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + sender, err := gateway.NewSender(p, i.Rng, getNDF(), &MockSendCMIXComms{t: t}, i.Session, nil) + if err != nil { + t.Errorf("%+v", errors.New(err.Error())) + return + } m := NewManager(i, params.Messages{ MessageReceptionBuffLen: 20, MessageReceptionWorkerPoolSize: 20, MaxChecksGarbledMessage: 20, GarbledMessageWait: time.Hour, - }, nil) + }, nil, sender) msgCmix := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen()) msgCmix.SetContents([]byte("test")) e2e.SetUnencrypted(msgCmix, m.Session.User().GetCryptographicIdentity().GetTransmissionID()) - _, _, err = sendCmixHelper(msgCmix, sess2.GetUser().ReceptionID, params.GetDefaultCMIX(), + _, _, err = sendCmixHelper(sender, msgCmix, sess2.GetUser().ReceptionID, params.GetDefaultCMIX(), m.Instance, m.Session, m.nodeRegistration, m.Rng, m.TransmissionID, &MockSendCMIXComms{t: t}) if err != nil { t.Errorf("Failed to sendcmix: %+v", err) + panic("t") + return } } func getNDF() *ndf.NetworkDefinition { + nodeId := id.NewIdFromString("zezima", id.Node, &testing.T{}) + gwId := nodeId.DeepCopy() + gwId.SetType(id.Gateway) return &ndf.NetworkDefinition{ E2E: ndf.Group{ Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" + @@ -168,5 +192,19 @@ func getNDF() *ndf.NetworkDefinition { "BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" + "DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", }, + Gateways: []ndf.Gateway{ + { + ID: gwId.Marshal(), + Address: "0.0.0.0", + TlsCertificate: "", + }, + }, + Nodes: []ndf.Node{ + { + ID: nodeId.Marshal(), + Address: "0.0.0.0", + TlsCertificate: "", + }, + }, } } diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go index 78061232830f167adf91d21f5805fa2e00b58916..16c14701d47ead025dae9758fa4697ce6c5ea007 100644 --- a/network/message/sendE2E.go +++ b/network/message/sendE2E.go @@ -95,7 +95,7 @@ func (m *Manager) SendE2E(msg message.Send, param params.E2E) ([]id.Round, e2e.M wg.Add(1) go func(i int) { var err error - roundIds[i], _, err = m.SendCMIX(msgEnc, msg.Recipient, + roundIds[i], _, err = m.SendCMIX(m.sender, msgEnc, msg.Recipient, param.CMIX) if err != nil { errCh <- err diff --git a/network/message/sendUnsafe.go b/network/message/sendUnsafe.go index ef28cf2d789d1f5f0a097413046b5a0c50b0a230..df7f05cb8df4ba368f3a3a19368dcb13214f7d49 100644 --- a/network/message/sendUnsafe.go +++ b/network/message/sendUnsafe.go @@ -64,7 +64,7 @@ func (m *Manager) SendUnsafe(msg message.Send, param params.Unsafe) ([]id.Round, wg.Add(1) go func(i int) { var err error - roundIds[i], _, err = m.SendCMIX(msgCmix, msg.Recipient, param.CMIX) + roundIds[i], _, err = m.SendCMIX(m.sender, msgCmix, msg.Recipient, param.CMIX) if err != nil { errCh <- err } diff --git a/network/node/register.go b/network/node/register.go index a27529b6eb612e9c3ae49a4e07393dbe67d8c347..c89d2dd55371bef074a3d417f5ca05ae66d4e550 100644 --- a/network/node/register.go +++ b/network/node/register.go @@ -13,6 +13,7 @@ import ( "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/cmix" @@ -33,14 +34,13 @@ import ( ) type RegisterNodeCommsInterface interface { - GetHost(hostId *id.ID) (*connect.Host, bool) SendRequestNonceMessage(host *connect.Host, message *pb.NonceRequest) (*pb.Nonce, error) SendConfirmNonceMessage(host *connect.Host, message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error) } -func StartRegistration(instance *network.Instance, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, +func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, c chan network.NodeGateway, numParallel uint) stoppable.Stoppable { multi := stoppable.NewMulti("NodeRegistrations") @@ -48,14 +48,14 @@ func StartRegistration(instance *network.Instance, session *storage.Session, rng for i := uint(0); i < numParallel; i++ { stop := stoppable.NewSingle(fmt.Sprintf("NodeRegistration %d", i)) - go registerNodes(session, rngGen, comms, stop, c) + go registerNodes(sender, session, rngGen, comms, stop, c) multi.Add(stop) } return multi } -func registerNodes(session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, +func registerNodes(sender *gateway.Sender, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, stop *stoppable.Single, c chan network.NodeGateway) { u := session.User() regSignature := u.GetTransmissionRegistrationValidationSignature() @@ -71,7 +71,7 @@ func registerNodes(session *storage.Session, rngGen *fastRNG.StreamGenerator, co t.Stop() return case gw := <-c: - err := registerWithNode(comms, gw, regSignature, uci, cmix, rng) + err := registerWithNode(sender, comms, gw, regSignature, uci, cmix, rng) if err != nil { jww.ERROR.Printf("Failed to register node: %+v", err) } @@ -82,7 +82,7 @@ func registerNodes(session *storage.Session, rngGen *fastRNG.StreamGenerator, co //registerWithNode serves as a helper for RegisterWithNodes // It registers a user with a specific in the client's ndf. -func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, regSig []byte, +func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface, ngw network.NodeGateway, regSig []byte, uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source) error { nodeID, err := ngw.Node.GetNodeId() if err != nil { @@ -109,7 +109,7 @@ func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, userNum := int(uci.GetTransmissionID().Bytes()[7]) h := sha256.New() h.Reset() - h.Write([]byte(strconv.Itoa(int(4000 + userNum)))) + h.Write([]byte(strconv.Itoa(4000 + userNum))) transmissionKey = store.GetGroup().NewIntFromBytes(h.Sum(nil)) jww.INFO.Printf("transmissionKey: %v", transmissionKey.Bytes()) @@ -118,7 +118,7 @@ func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, // keys transmissionHash, _ := hash.NewCMixHash() - nonce, dhPub, err := requestNonce(comms, gatewayID, regSig, uci, store, rng) + nonce, dhPub, err := requestNonce(sender, comms, gatewayID, regSig, uci, store, rng) if err != nil { return errors.Errorf("Failed to request nonce: %+v", err) } @@ -127,8 +127,8 @@ func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, serverPubDH := store.GetGroup().NewIntFromBytes(dhPub) // Confirm received nonce - jww.INFO.Println("Register: Confirming received nonce") - err = confirmNonce(comms, uci.GetTransmissionID().Bytes(), + jww.INFO.Printf("Register: Confirming received nonce from node %s", nodeID.String()) + err = confirmNonce(sender, comms, uci.GetTransmissionID().Bytes(), nonce, uci.GetTransmissionRSA(), gatewayID) if err != nil { errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err) @@ -145,7 +145,7 @@ func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, return nil } -func requestNonce(comms RegisterNodeCommsInterface, gwId *id.ID, regHash []byte, +func requestNonce(sender *gateway.Sender, comms RegisterNodeCommsInterface, gwId *id.ID, regHash []byte, uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source) ([]byte, []byte, error) { dhPub := store.GetDHPublicKey().Bytes() opts := rsa.NewDefaultOptions() @@ -162,34 +162,37 @@ func requestNonce(comms RegisterNodeCommsInterface, gwId *id.ID, regHash []byte, } // Request nonce message from gateway - jww.INFO.Printf("Register: Requesting nonce from gateway %v", - gwId.Bytes()) - - host, ok := comms.GetHost(gwId) - if !ok { - return nil, nil, errors.Errorf("Failed to find host with ID %s", gwId.String()) - } - nonceResponse, err := comms.SendRequestNonceMessage(host, - &pb.NonceRequest{ - Salt: uci.GetTransmissionSalt(), - ClientRSAPubKey: string(rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())), - ClientSignedByServer: &messages.RSASignature{ - Signature: regHash, - }, - ClientDHPubKey: dhPub, - RequestSignature: &messages.RSASignature{ - Signature: clientSig, - }, - }) - + jww.INFO.Printf("Register: Requesting nonce from gateway %v", gwId.String()) + + result, err := sender.SendToAny(func(host *connect.Host) (interface{}, error) { + nonceResponse, err := comms.SendRequestNonceMessage(host, + &pb.NonceRequest{ + Salt: uci.GetTransmissionSalt(), + ClientRSAPubKey: string(rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())), + ClientSignedByServer: &messages.RSASignature{ + Signature: regHash, + }, + ClientDHPubKey: dhPub, + RequestSignature: &messages.RSASignature{ + Signature: clientSig, + }, + Target: gwId.Marshal(), + }) + if err != nil { + errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err) + return nil, errors.New(errMsg) + } + if nonceResponse.Error != "" { + err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error)) + return nil, err + } + return nonceResponse, nil + }) if err != nil { - errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err) - return nil, nil, errors.New(errMsg) - } - if nonceResponse.Error != "" { - err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error)) return nil, nil, err } + nonceResponse := result.(*pb.Nonce) + // Use Client keypair to sign Server nonce return nonceResponse.Nonce, nonceResponse.DHPubKey, nil } @@ -197,7 +200,7 @@ func requestNonce(comms RegisterNodeCommsInterface, gwId *id.ID, regHash []byte, // confirmNonce is a helper for the Register function // It signs a nonce and sends it for confirmation // Returns nil if successful, error otherwise -func confirmNonce(comms RegisterNodeCommsInterface, UID, nonce []byte, +func confirmNonce(sender *gateway.Sender, comms RegisterNodeCommsInterface, UID, nonce []byte, privateKeyRSA *rsa.PrivateKey, gwID *id.ID) error { opts := rsa.NewDefaultOptions() opts.Hash = hash.CMixHash @@ -224,22 +227,19 @@ func confirmNonce(comms RegisterNodeCommsInterface, UID, nonce []byte, NonceSignedByClient: &messages.RSASignature{ Signature: sig, }, + Target: gwID.Marshal(), } - host, ok := comms.GetHost(gwID) - if !ok { - return errors.Errorf("Failed to find host with ID %s", gwID.String()) - } - confirmResponse, err := comms.SendConfirmNonceMessage(host, msg) - if err != nil { - err := errors.New(fmt.Sprintf( - "confirmNonce: Unable to send signed nonce! %s", err)) - return err - } - if confirmResponse.Error != "" { - err := errors.New(fmt.Sprintf( - "confirmNonce: Error confirming nonce: %s", confirmResponse.Error)) - return err - } - return nil + _, err = sender.SendToAny(func(host *connect.Host) (interface{}, error) { + confirmResponse, err := comms.SendConfirmNonceMessage(host, msg) + if err != nil { + return nil, err + } else if confirmResponse.Error != "" { + err := errors.New(fmt.Sprintf( + "confirmNonce: Error confirming nonce: %s", confirmResponse.Error)) + return nil, err + } + return confirmResponse, nil + }) + return err } diff --git a/network/polltracker.go b/network/polltracker.go index 48574c7d8e19eb4e1d73090af0c017e3c9b8187b..63739e19afbb1a959face8c7859f23c704a7cb66 100644 --- a/network/polltracker.go +++ b/network/polltracker.go @@ -13,7 +13,7 @@ func newPollTracker() *pollTracker { return &pt } -//tracks a single poll +// Track tracks a single poll func (pt *pollTracker) Track(ephID ephemeral.Id, source *id.ID) { if _, exists := (*pt)[*source]; !exists { (*pt)[*source] = make(map[int64]uint) @@ -25,7 +25,7 @@ func (pt *pollTracker) Track(ephID ephemeral.Id, source *id.ID) { } } -//reports all resent polls +// Report reports all recent polls func (pt *pollTracker) Report() string { report := "" numReports := uint(0) diff --git a/network/rounds/check.go b/network/rounds/check.go index a22bafd8e7931ecbbe673b2df1c071bad4fb329c..03cd83718495b71ef5455e9112dfe95fdda89fe3 100644 --- a/network/rounds/check.go +++ b/network/rounds/check.go @@ -11,6 +11,7 @@ import ( "encoding/binary" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage/reception" + "gitlab.com/elixxir/client/storage/rounds" "gitlab.com/xx_network/primitives/id" ) @@ -27,7 +28,12 @@ import ( // Retrieval // false: no message // true: message -func Checker(roundID id.Round, filters []*RemoteFilter) bool { +func Checker(roundID id.Round, filters []*RemoteFilter, cr *rounds.CheckedRounds) bool { + // Skip checking if the round is already checked + if cr.IsChecked(roundID) { + return true + } + //find filters that could have the round and check them serialRid := serializeRound(roundID) for _, filter := range filters { diff --git a/network/rounds/historical.go b/network/rounds/historical.go index 6d2d76de30a44151e55cecc863f2113eb2a7a3fb..45aed7fdfaa7b296a46de79645e19fd010f88634 100644 --- a/network/rounds/historical.go +++ b/network/rounds/historical.go @@ -9,7 +9,6 @@ package rounds import ( jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/storage/reception" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/comms/connect" @@ -87,13 +86,6 @@ func (m *Manager) processHistoricalRounds(comm historicalRoundsComms, quitCh <-c continue } - //find a gateway to request about the roundRequests - gwHost, err := gateway.Get(m.Instance.GetPartialNdf().Get(), comm, rng) - if err != nil { - jww.FATAL.Panicf("Failed to track network, NDF has corrupt "+ - "data: %s", err) - } - rounds := make([]uint64, len(roundRequests)) for i, rr := range roundRequests { rounds[i] = uint64(rr.rid) @@ -104,18 +96,21 @@ func (m *Manager) processHistoricalRounds(comm historicalRoundsComms, quitCh <-c Rounds: rounds, } - jww.DEBUG.Printf("Requesting Historical rounds %v from "+ - "gateway %s", rounds, gwHost.GetId()) + result, err := m.sender.SendToAny(func(host *connect.Host) (interface{}, error) { + jww.DEBUG.Printf("Requesting Historical rounds %v from "+ + "gateway %s", rounds, host.GetId()) + return comm.RequestHistoricalRounds(host, hr) + }) - response, err := comm.RequestHistoricalRounds(gwHost, hr) if err != nil { jww.ERROR.Printf("Failed to request historical roundRequests "+ - "data for rounds %v: %s", rounds, response) + "data for rounds %v: %s", rounds, err) // if the check fails to resolve, break the loop and so they will be // checked again timerCh = time.NewTimer(m.params.HistoricalRoundsPeriod).C continue } + response := result.(*pb.HistoricalRoundsResponse) // process the returned historical roundRequests. for i, roundInfo := range response.Rounds { diff --git a/network/rounds/manager.go b/network/rounds/manager.go index 6e94ef391a3812af4029298d225ec74ccf03e4aa..942e86319efe8ab05711b901360edbcd37865978 100644 --- a/network/rounds/manager.go +++ b/network/rounds/manager.go @@ -10,6 +10,7 @@ package rounds import ( "fmt" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/internal" "gitlab.com/elixxir/client/network/message" "gitlab.com/elixxir/client/stoppable" @@ -19,6 +20,7 @@ type Manager struct { params params.Rounds internal.Internal + sender *gateway.Sender historicalRounds chan historicalRoundRequest lookupRoundMessages chan roundLookup @@ -26,13 +28,14 @@ type Manager struct { } func NewManager(internal internal.Internal, params params.Rounds, - bundles chan<- message.Bundle) *Manager { + bundles chan<- message.Bundle, sender *gateway.Sender) *Manager { m := &Manager{ params: params, historicalRounds: make(chan historicalRoundRequest, params.HistoricalRoundsBufferLen), lookupRoundMessages: make(chan roundLookup, params.LookupRoundsBufferLen), messageBundles: bundles, + sender: sender, } m.Internal = internal diff --git a/network/rounds/retrieve.go b/network/rounds/retrieve.go index effd66b7fcac84db95255cc458c997a36d134f2b..0d90355c64bb2adaff003442737e09468a5b05f8 100644 --- a/network/rounds/retrieve.go +++ b/network/rounds/retrieve.go @@ -10,7 +10,6 @@ package rounds import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/message" "gitlab.com/elixxir/client/storage/reception" pb "gitlab.com/elixxir/comms/mixmessages" @@ -44,52 +43,35 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, done = true case rl := <-m.lookupRoundMessages: ri := rl.roundInfo - var bundle message.Bundle - // Get a shuffled list of gateways in the round - gwHosts, err := gateway.GetAllShuffled(comms, ri) - if err != nil { - jww.WARN.Printf("Failed to get gateway hosts from "+ - "round %v, not requesting from them", - ri.ID) - break - } - - // Attempt to request messages for every gateway in the list. - // If we retrieve without error, then we exit. If we error, then - // we retry with the next gateway in the list until we exhaust the list - for i, gwHost := range gwHosts { - // Attempt to request for this gateway - bundle, err = m.getMessagesFromGateway(id.Round(ri.ID), rl.identity, comms, gwHost) + // Convert gateways in round to proper ID format + gwIds := make([]*id.ID, len(ri.Topology)) + for i, idBytes := range ri.Topology { + gwId, err := id.Unmarshal(idBytes) if err != nil { - - jww.WARN.Printf("Failed on gateway [%d/%d] to get messages for round %v", - i, len(gwHosts), ri.ID) - - // Retry for the next gateway in the list - continue + jww.FATAL.Panicf("processMessageRetrieval: Unable to unmarshal: %+v", err) } - - // If a non-error request, no longer retry - break - - } - gwIDs := make([]*id.ID, 0) - for _, gwHost := range gwHosts { - gwIDs = append(gwIDs, gwHost.GetId()) + gwId.SetType(id.Gateway) + gwIds[i] = gwId } + // Attempt to request for this gateway + bundle, err := m.getMessagesFromGateway(id.Round(ri.ID), rl.identity, comms, gwIds) + // After trying all gateways, if none returned we mark the round as a // failure and print out the last error if err != nil { jww.ERROR.Printf("Failed to get pickup round %d "+ - "from all gateways (%v): final gateway %s returned : %s", - id.Round(ri.ID), gwIDs, gwHosts[len(gwHosts)-1].GetId(), err) - } else if len(bundle.Messages) != 0 { + "from all gateways (%v): %s", + id.Round(ri.ID), gwIds, err) + } + + if len(bundle.Messages) != 0 { // If successful and there are messages, we send them to another thread bundle.Identity = rl.identity m.messageBundles <- bundle } + } } } @@ -97,40 +79,49 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, // getMessagesFromGateway attempts to get messages from their assigned // gateway host in the round specified. If successful func (m *Manager) getMessagesFromGateway(roundID id.Round, identity reception.IdentityUse, - comms messageRetrievalComms, gwHost *connect.Host) (message.Bundle, error) { + comms messageRetrievalComms, gwIds []*id.ID) (message.Bundle, error) { + + // Send to the gateways using backup proxies + result, err := m.sender.SendToPreferred(gwIds, func(host *connect.Host, target *id.ID) (interface{}, error) { + jww.DEBUG.Printf("Trying to get messages for round %v for ephemeralID %d (%v) "+ + "via Gateway: %s", roundID, identity.EphId.Int64(), identity.Source.String(), host.GetId()) + + // send the request + msgReq := &pb.GetMessages{ + ClientID: identity.EphId[:], + RoundID: uint64(roundID), + Target: target.Marshal(), + } - jww.DEBUG.Printf("Trying to get messages for round %v for ephmeralID %d (%v) "+ - "via Gateway: %s", roundID, identity.EphId.Int64(), identity.Source.String(), gwHost.GetId()) + // If the gateway doesnt have the round, return an error + msgResp, err := comms.RequestMessages(host, msgReq) + if err == nil && !msgResp.GetHasRound() { + return message.Bundle{}, errors.Errorf(noRoundError) + } + + return msgResp, err + }) - // send the request - msgReq := &pb.GetMessages{ - ClientID: identity.EphId[:], - RoundID: uint64(roundID), - } - msgResp, err := comms.RequestMessages(gwHost, msgReq) // Fail the round if an error occurs so it can be tried again later if err != nil { return message.Bundle{}, errors.WithMessagef(err, "Failed to "+ - "request messages from %s for round %d", gwHost.GetId(), roundID) - } - // if the gateway doesnt have the round, return an error - if !msgResp.GetHasRound() { - return message.Bundle{}, errors.Errorf(noRoundError) + "request messages for round %d", roundID) } + msgResp := result.(*pb.GetMessagesResponse) // If there are no messages print a warning. Due to the probabilistic nature // of the bloom filters, false positives will happen some times msgs := msgResp.GetMessages() if msgs == nil || len(msgs) == 0 { - jww.WARN.Printf("host %s has no messages for client %s "+ + jww.WARN.Printf("no messages for client %s "+ " in round %d. This happening every once in a while is normal,"+ - " but can be indicitive of a problem if it is consistant", gwHost, + " but can be indicative of a problem if it is consistent", m.TransmissionID, roundID) return message.Bundle{}, nil } - jww.INFO.Printf("Received %d messages in Round %v via Gateway %s for %d (%s)", - len(msgs), roundID, gwHost.GetId(), identity.EphId.Int64(), identity.Source) + jww.INFO.Printf("Received %d messages in Round %v for %d (%s)", + len(msgs), roundID, identity.EphId.Int64(), identity.Source) //build the bundle of messages to send to the message processor bundle := message.Bundle{ diff --git a/network/rounds/retrieve_test.go b/network/rounds/retrieve_test.go index 2efd2c89c6865ba335d2e167cab343bb786370db..abd79533dd9189cdef93f8f37aba4b8ad810b8d2 100644 --- a/network/rounds/retrieve_test.go +++ b/network/rounds/retrieve_test.go @@ -8,11 +8,15 @@ package rounds import ( "bytes" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/network/message" "gitlab.com/elixxir/client/storage/reception" pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" "reflect" "testing" "time" @@ -25,6 +29,17 @@ func TestManager_ProcessMessageRetrieval(t *testing.T) { roundId := id.Round(5) mockComms := &mockMessageRetrievalComms{testingSignature: t} quitChan := make(chan struct{}) + testNdf := getNDF() + nodeId := id.NewIdFromString(ReturningGateway, id.Node, &testing.T{}) + gwId := nodeId.DeepCopy() + gwId.SetType(id.Gateway) + testNdf.Gateways = []ndf.Gateway{{ID: gwId.Marshal()}} + + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + testManager.sender, _ = gateway.NewSender(p, + fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), + testNdf, mockComms, testManager.Session, nil) // Create a local channel so reception is possible (testManager.messageBundles is // send only via newManager call above) @@ -103,8 +118,19 @@ func TestManager_ProcessMessageRetrieval(t *testing.T) { func TestManager_ProcessMessageRetrieval_NoRound(t *testing.T) { // General initializations testManager := newManager(t) + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 roundId := id.Round(5) mockComms := &mockMessageRetrievalComms{testingSignature: t} + testNdf := getNDF() + nodeId := id.NewIdFromString(FalsePositive, id.Node, &testing.T{}) + gwId := nodeId.DeepCopy() + gwId.SetType(id.Gateway) + testNdf.Gateways = []ndf.Gateway{{ID: gwId.Marshal()}} + + testManager.sender, _ = gateway.NewSender(p, + fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), + testNdf, mockComms, testManager.Session, nil) quitChan := make(chan struct{}) // Create a local channel so reception is possible (testManager.messageBundles is @@ -130,7 +156,7 @@ func TestManager_ProcessMessageRetrieval_NoRound(t *testing.T) { }, } - idList := [][]byte{dummyGateway.Bytes()} + idList := [][]byte{dummyGateway.Marshal()} roundInfo := &pb.RoundInfo{ ID: uint64(roundId), @@ -172,6 +198,17 @@ func TestManager_ProcessMessageRetrieval_FalsePositive(t *testing.T) { roundId := id.Round(5) mockComms := &mockMessageRetrievalComms{testingSignature: t} quitChan := make(chan struct{}) + testNdf := getNDF() + nodeId := id.NewIdFromString(FalsePositive, id.Node, &testing.T{}) + gwId := nodeId.DeepCopy() + gwId.SetType(id.Gateway) + testNdf.Gateways = []ndf.Gateway{{ID: gwId.Marshal()}} + + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + testManager.sender, _ = gateway.NewSender(p, + fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), + testNdf, mockComms, testManager.Session, nil) // Create a local channel so reception is possible (testManager.messageBundles is // send only via newManager call above) @@ -306,6 +343,17 @@ func TestManager_ProcessMessageRetrieval_MultipleGateways(t *testing.T) { roundId := id.Round(5) mockComms := &mockMessageRetrievalComms{testingSignature: t} quitChan := make(chan struct{}) + testNdf := getNDF() + nodeId := id.NewIdFromString(ReturningGateway, id.Node, &testing.T{}) + gwId := nodeId.DeepCopy() + gwId.SetType(id.Gateway) + testNdf.Gateways = []ndf.Gateway{{ID: gwId.Marshal()}} + + p := gateway.DefaultPoolParams() + p.MaxPoolSize = 1 + testManager.sender, _ = gateway.NewSender(p, + fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), + testNdf, mockComms, testManager.Session, nil) // Create a local channel so reception is possible (testManager.messageBundles is // send only via newManager call above) diff --git a/network/rounds/utils_test.go b/network/rounds/utils_test.go index c963199bedc637e13a050ac9c08d769d8c4a7acf..d352078dcd1219b188c8c7fde0b807748d8c3521 100644 --- a/network/rounds/utils_test.go +++ b/network/rounds/utils_test.go @@ -14,6 +14,7 @@ import ( pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/ndf" "testing" ) @@ -28,7 +29,6 @@ func newManager(face interface{}) *Manager { TransmissionID: sess1.GetUser().TransmissionID, }, } - return testManager } @@ -43,6 +43,15 @@ type mockMessageRetrievalComms struct { testingSignature *testing.T } +func (mmrc *mockMessageRetrievalComms) AddHost(hid *id.ID, address string, cert []byte, params connect.HostParams) (host *connect.Host, err error) { + host, _ = mmrc.GetHost(nil) + return host, nil +} + +func (mmrc *mockMessageRetrievalComms) RemoveHost(hid *id.ID) { + +} + func (mmrc *mockMessageRetrievalComms) GetHost(hostId *id.ID) (*connect.Host, bool) { h, _ := connect.NewHost(hostId, "0.0.0.0", []byte(""), connect.HostParams{ MaxRetries: 0, @@ -92,3 +101,42 @@ func (mmrc *mockMessageRetrievalComms) RequestMessages(host *connect.Host, return nil, nil } + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" + + "7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" + + "DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" + + "8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" + + "023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" + + "3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" + + "6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" + + "A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" + + "37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" + + "78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" + + "015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" + + "6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" + + "847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" + + "C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" + + "FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" + + "B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" + + "35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" + + "F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" + + "92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" + + "3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" + + "D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" + + "6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" + + "085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" + + "AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" + + "3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" + + "BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" + + "DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +} diff --git a/network/send.go b/network/send.go index 0e3e9020aa187716e3a7dd7da9f538bff47ba3e9..d70a0c6661c2ee6f4c40f313219baa7cbb86fd52 100644 --- a/network/send.go +++ b/network/send.go @@ -28,7 +28,7 @@ func (m *manager) SendCMIX(msg format.Message, recipient *id.ID, param params.CM "network is not healthy") } - return m.message.SendCMIX(msg, recipient, param) + return m.message.SendCMIX(m.GetSender(), msg, recipient, param) } // SendUnsafe sends an unencrypted payload to the provided recipient diff --git a/single/manager_test.go b/single/manager_test.go index 5ee50ed606ff203253729aae941e248c224df533..2ce788f7b86ba44fd61a2aaa68814d1d62a49a02 100644 --- a/single/manager_test.go +++ b/single/manager_test.go @@ -14,6 +14,7 @@ import ( "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/reception" @@ -323,6 +324,10 @@ func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 } +func (t *testNetworkManager) GetSender() *gateway.Sender { + return nil +} + func getNDF() *ndf.NetworkDefinition { return &ndf.NetworkDefinition{ E2E: ndf.Group{ diff --git a/storage/auth/store.go b/storage/auth/store.go index a03dee9eaced47223f597f14d78c6c093ede6cfd..d6730d03d8fcde7db0ee9edb76cb056942be8b7c 100644 --- a/storage/auth/store.go +++ b/storage/auth/store.go @@ -355,17 +355,18 @@ func (s *Store) GetRequest(partner *id.ID) (RequestType, *SentRequest, contact.C } } -// Fail is one of two calls after using a request. This one is to be used when +// Done is one of two calls after using a request. This one is to be used when // the use is unsuccessful. It will allow any thread waiting on access to // continue using the structure. // It does not return an error because an error is not handleable. -func (s *Store) Fail(partner *id.ID) { +func (s *Store) Done(partner *id.ID) { s.mux.RLock() r, ok := s.requests[*partner] s.mux.RUnlock() if !ok { - jww.ERROR.Panicf("Request cannot be failed, not found: %s", partner) + jww.ERROR.Panicf("Request cannot be finished, not " + + "found: %s", partner) return } diff --git a/storage/auth/store_test.go b/storage/auth/store_test.go index 94d0318f3d702cd7f9fb58860ada31a1b6f54b17..9f94169dc07eefe12c2d7ec9685d3d256fa2f326 100644 --- a/storage/auth/store_test.go +++ b/storage/auth/store_test.go @@ -526,11 +526,11 @@ func TestStore_Fail(t *testing.T) { } }() - s.Fail(c.ID) + s.Done(c.ID) // Check if the request's mutex is locked if reflect.ValueOf(&s.requests[*c.ID].mux).Elem().FieldByName("state").Int() != 0 { - t.Errorf("Fail() did not unlock mutex.") + t.Errorf("Done() did not unlock mutex.") } } @@ -540,11 +540,11 @@ func TestStore_Fail_RequestNotInMap(t *testing.T) { defer func() { if r := recover(); r == nil { - t.Errorf("Fail() did not panic when the request is not in map.") + t.Errorf("Done() did not panic when the request is not in map.") } }() - s.Fail(id.NewIdFromUInt(rand.Uint64(), id.User, t)) + s.Done(id.NewIdFromUInt(rand.Uint64(), id.User, t)) } // Happy path: receive request. diff --git a/storage/reception/IdentityUse.go b/storage/reception/IdentityUse.go index 93fc6b8d6c374cbf166d1a55e3c302a46192704f..2c8b9df3b64601651d7489e96935ff1a3d360db9 100644 --- a/storage/reception/IdentityUse.go +++ b/storage/reception/IdentityUse.go @@ -22,6 +22,7 @@ type IdentityUse struct { UR *rounds.UnknownRounds ER *rounds.EarliestRound + CR *rounds.CheckedRounds } // setSamplingPeriod add the Request mask as a random buffer around the sampling diff --git a/storage/reception/fake_test.go b/storage/reception/fake_test.go index 35ff0e1727a76645ef350b8d1e4a7cd5a76691e9..2c748ac2258e0950e8901a2748c0057c498770ef 100644 --- a/storage/reception/fake_test.go +++ b/storage/reception/fake_test.go @@ -24,7 +24,7 @@ func Test_generateFakeIdentity(t *testing.T) { "\"EndValid\":" + string(endValid) + "," + "\"RequestMask\":86400000000000,\"Ephemeral\":true," + "\"StartRequest\":\"0001-01-01T00:00:00Z\"," + - "\"EndRequest\":\"0001-01-01T00:00:00Z\",\"Fake\":true,\"UR\":null,\"ER\":null}" + "\"EndRequest\":\"0001-01-01T00:00:00Z\",\"Fake\":true,\"UR\":null,\"ER\":null,\"CR\":null}" timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) @@ -36,8 +36,8 @@ func Test_generateFakeIdentity(t *testing.T) { receivedJson, _ := json.Marshal(received) if expected != string(receivedJson) { - t.Errorf("The fake identity was generated incorrectly.\n "+ - "expected: %s\nreceived: %s", expected, receivedJson) + t.Errorf("The fake identity was generated incorrectly."+ + "\nexpected: %s\nreceived: %s", expected, receivedJson) } } diff --git a/storage/reception/registration.go b/storage/reception/registration.go index 70647129c7c621b9a757b9d42ec82ef66f3c1bd3..0535f092366ce03f556f21ad61b39b8b65a97b63 100644 --- a/storage/reception/registration.go +++ b/storage/reception/registration.go @@ -2,6 +2,8 @@ package reception import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/storage/rounds" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/xx_network/primitives/id" @@ -17,6 +19,7 @@ type registration struct { Identity UR *rounds.UnknownRounds ER *rounds.EarliestRound + CR *rounds.CheckedRounds kv *versioned.KV } @@ -46,6 +49,11 @@ func newRegistration(reg Identity, kv *versioned.KV) (*registration, error) { urParams.Stored = !reg.Ephemeral r.UR = rounds.NewUnknownRounds(kv, urParams) r.ER = rounds.NewEarliestRound(!reg.Ephemeral, kv) + cr, err := rounds.NewCheckedRounds(int(params.GetDefaultNetwork().KnownRoundsThreshold), kv) + if err != nil { + jww.FATAL.Printf("Failed to create new CheckedRounds for registration: %+v", err) + } + r.CR = cr // If this is not ephemeral, then store everything if !reg.Ephemeral { @@ -71,11 +79,24 @@ func loadRegistration(EphId ephemeral.Id, Source *id.ID, startValid time.Time, "for %s", regPrefix(EphId, Source, startValid)) } + cr, err := rounds.LoadCheckedRounds(int(params.GetDefaultNetwork().KnownRoundsThreshold), kv) + if err != nil { + jww.ERROR.Printf("Making new CheckedRounds, loading of CheckedRounds "+ + "failed: %+v", err) + + cr, err = rounds.NewCheckedRounds(int(params.GetDefaultNetwork().KnownRoundsThreshold), kv) + if err != nil { + jww.FATAL.Printf("Failed to create new CheckedRounds for "+ + "registration after CheckedRounds load failure: %+v", err) + } + } + r := ®istration{ Identity: reg, kv: kv, UR: rounds.LoadUnknownRounds(kv, rounds.DefaultUnknownRoundsParams()), ER: rounds.LoadEarliestRound(kv), + CR: cr, } return r, nil diff --git a/storage/reception/store.go b/storage/reception/store.go index f87ee416abaecd01e3955549080e462b3ae1e3ad..bd78f0b7682d3c06c2b47211f518729cb63cbfc1 100644 --- a/storage/reception/store.go +++ b/storage/reception/store.go @@ -382,5 +382,6 @@ func (s *Store) selectIdentity(rng io.Reader, now time.Time) (IdentityUse, error Fake: false, UR: selected.UR, ER: selected.ER, + CR: selected.CR, }, nil } diff --git a/storage/rounds/checkedRounds.go b/storage/rounds/checkedRounds.go new file mode 100644 index 0000000000000000000000000000000000000000..a7e3ebdecd425a3ba97902a2197fd3189b7b6f59 --- /dev/null +++ b/storage/rounds/checkedRounds.go @@ -0,0 +1,164 @@ +package rounds + +import ( + "container/list" + "encoding/binary" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/utility" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/id" +) + +const itemsPerBlock = 50 + +type CheckedRounds struct { + // Map of round IDs for quick lookup if an ID is stored + m map[id.Round]interface{} + + // List of round IDs in order of age; oldest in front and newest in back + l *list.List + + // List of recently added round IDs that need to be stored + recent []id.Round + + // Saves round IDs in blocks to storage + store *utility.BlockStore + + // The maximum number of round IDs to store before pruning the oldest + maxRounds int +} + +// NewCheckedRounds returns a new CheckedRounds with an initialized map. +func NewCheckedRounds(maxRounds int, kv *versioned.KV) (*CheckedRounds, error) { + // Calculate the number of blocks of size itemsPerBlock are needed to store + // numRoundsToKeep number of round IDs + numBlocks := maxRounds / itemsPerBlock + if maxRounds%itemsPerBlock != 0 { + numBlocks++ + } + + // Create a new BlockStore for storing the round IDs to storage + store, err := utility.NewBlockStore(itemsPerBlock, numBlocks, kv) + if err != nil { + return nil, errors.Errorf("failed to save new checked rounds to storage: %+v", err) + } + + // Create new CheckedRounds + return newCheckedRounds(maxRounds, store), nil +} + +// newCheckedRounds initialises the lists in CheckedRounds. +func newCheckedRounds(maxRounds int, store *utility.BlockStore) *CheckedRounds { + return &CheckedRounds{ + m: make(map[id.Round]interface{}), + l: list.New(), + recent: []id.Round{}, + store: store, + maxRounds: maxRounds, + } +} + +// LoadCheckedRounds restores the list from storage. +func LoadCheckedRounds(maxRounds int, kv *versioned.KV) (*CheckedRounds, error) { + // Get rounds from storage + store, rounds, err := utility.LoadBlockStore(kv) + if err != nil { + return nil, errors.Errorf("failed to load CheckedRounds from storage: %+v", err) + } + + // Create new CheckedRounds + cr := newCheckedRounds(maxRounds, store) + + // Unmarshal round ID byte list into the new CheckedRounds + cr.unmarshal(rounds) + + return cr, nil +} + +// SaveCheckedRounds stores the list to storage. +func (cr *CheckedRounds) SaveCheckedRounds() error { + + // Save to disk + err := cr.store.Store(cr) + if err != nil { + return errors.Errorf("failed to store recent CheckedRounds: %+v", err) + } + + // Save to disk + return nil +} + +// Next pops the oldest recent round ID from the list and returns it as bytes. +// Returns false if the list is empty +func (cr *CheckedRounds) Next() ([]byte, bool) { + if len(cr.recent) > 0 { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(cr.recent[0])) + cr.recent = cr.recent[1:] + return b, true + } + + return nil, false +} + +// Check determines if the round ID has been added to the checklist. If it has +// not, then it is added and the function returns true. Otherwise, if it already +// exists, then the function returns false. +func (cr *CheckedRounds) Check(rid id.Round) bool { + // Add the round ID to the checklist if it does not exist and return true + if _, exists := cr.m[rid]; !exists { + cr.m[rid] = nil // Add ID to the map + cr.l.PushBack(rid) // Add ID to the end of the list + cr.recent = append(cr.recent, rid) + + // The commented out code below works the same as the Prune function but + // occurs when adding a round ID to the list. It was decided to use + // Prune instead so that it does not block even though the savings are + // probably negligible. + // // Remove the oldest round ID the list is full + // if cr.l.Len() > cr.maxRounds { + // oldestID := cr.l.Remove(cr.l.Front()) // Remove oldest from list + // delete(cr.m, oldestID.(id.Round)) // Remove oldest from map + // } + + return true + } + + return false +} + +// IsChecked determines if the round has been added to the checklist. +func (cr *CheckedRounds) IsChecked(rid id.Round) bool { + _, exists := cr.m[rid] + return exists +} + +// Prune any rounds that are earlier than the earliestAllowed. +func (cr *CheckedRounds) Prune() { + if len(cr.m) < cr.maxRounds { + return + } + earliestAllowed := cr.l.Back().Value.(id.Round) - id.Round(cr.maxRounds) + 1 + + // Iterate over all the round IDs and remove any that are too old + for e := cr.l.Front(); e != nil; { + if e.Value.(id.Round) < earliestAllowed { + delete(cr.m, e.Value.(id.Round)) + lastE := e + e = e.Next() + cr.l.Remove(lastE) + } else { + break + } + } +} + +// unmarshal unmarshalls the list of byte slices into the CheckedRounds map and +// list. +func (cr *CheckedRounds) unmarshal(rounds [][]byte) { + for _, round := range rounds { + rid := id.Round(binary.LittleEndian.Uint64(round)) + cr.m[rid] = nil + cr.l.PushBack(rid) + } +} diff --git a/storage/rounds/checkedRounds_test.go b/storage/rounds/checkedRounds_test.go new file mode 100644 index 0000000000000000000000000000000000000000..816c6fce7c311b71c5dd2d33f4b5fa07b2ec7ec9 --- /dev/null +++ b/storage/rounds/checkedRounds_test.go @@ -0,0 +1,217 @@ +package rounds + +import ( + "container/list" + "encoding/binary" + "gitlab.com/elixxir/client/storage/utility" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" +) + +// Happy path. +func Test_newCheckedRounds(t *testing.T) { + maxRounds := 230 + kv := versioned.NewKV(make(ekv.Memstore)) + + // Create a new BlockStore for storing the round IDs to storage + store, err := utility.NewBlockStore(itemsPerBlock, maxRounds/itemsPerBlock+1, kv) + if err != nil { + t.Errorf("Failed to create new BlockStore: %+v", err) + } + + expected := &CheckedRounds{ + m: make(map[id.Round]interface{}), + l: list.New(), + recent: []id.Round{}, + store: store, + maxRounds: maxRounds, + } + + received, err := NewCheckedRounds(maxRounds, kv) + if err != nil { + t.Errorf("NewCheckedRounds() returned an error: %+v", err) + } + + if !reflect.DeepEqual(expected, received) { + t.Errorf("NewCheckedRounds() did not return the exepcted CheckedRounds."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } +} + +// Tests that a CheckedRounds that has been saved and loaded from storage +// matches the original. +func TestCheckedRounds_SaveCheckedRounds_TestLoadCheckedRounds(t *testing.T) { + // Create new CheckedRounds and add rounds to it + kv := versioned.NewKV(make(ekv.Memstore)) + cr, err := NewCheckedRounds(50, kv) + if err != nil { + t.Errorf("failed to make new CheckedRounds: %+v", err) + } + for i := id.Round(0); i < 100; i++ { + cr.Check(i) + } + + err = cr.SaveCheckedRounds() + if err != nil { + t.Errorf("SaveCheckedRounds() returned an error: %+v", err) + } + + cr.Prune() + + newCR, err := LoadCheckedRounds(50, kv) + if err != nil { + t.Errorf("LoadCheckedRounds() returned an error: %+v", err) + } + + if !reflect.DeepEqual(cr, newCR) { + t.Errorf("Failed to store and load CheckedRounds."+ + "\nexpected: %+v\nreceived: %+v", cr, newCR) + } +} + +// Happy path. +func TestCheckedRounds_Next(t *testing.T) { + cr := newCheckedRounds(100, nil) + rounds := make([][]byte, 10) + for i := id.Round(0); i < 10; i++ { + cr.Check(i) + } + + for i := id.Round(0); i < 10; i++ { + round, exists := cr.Next() + if !exists { + t.Error("Next() returned false when there should be more IDs.") + } + + rounds[i] = round + } + round, exists := cr.Next() + if exists { + t.Errorf("Next() returned true when the list should be empty: %d", round) + } + + testCR := newCheckedRounds(100, nil) + testCR.unmarshal(rounds) + + if !reflect.DeepEqual(cr, testCR) { + t.Errorf("unmarshal() did not return the expected CheckedRounds."+ + "\nexpected: %+v\nreceived: %+v", cr, testCR) + } +} + +// Happy path. +func Test_checkedRounds_Check(t *testing.T) { + cr := newCheckedRounds(100, nil) + var expected []id.Round + for i := id.Round(1); i < 11; i++ { + if i%2 == 0 { + if !cr.Check(i) { + t.Errorf("Check() returned false when the round ID should have been added (%d).", i) + } + + val := cr.l.Back().Value.(id.Round) + if val != i { + t.Errorf("Check() did not add the round ID to the back of the list."+ + "\nexpected: %d\nreceived: %d", i, val) + } + expected = append(expected, i) + } + } + + if !reflect.DeepEqual(cr.recent, expected) { + t.Errorf("Unexpected list of recent rounds."+ + "\nexpected: %+v\nreceived: %+v", expected, cr.recent) + } + + for i := id.Round(1); i < 11; i++ { + result := cr.Check(i) + if i%2 == 0 { + if result { + t.Errorf("Check() returned true when the round ID should not have been added (%d).", i) + } + } else if !result { + t.Errorf("Check() returned false when the round ID should have been added (%d).", i) + } else { + expected = append(expected, i) + } + } + + if !reflect.DeepEqual(cr.recent, expected) { + t.Errorf("Unexpected list of recent rounds."+ + "\nexpected: %+v\nreceived: %+v", expected, cr.recent) + } +} + +// Happy path. +func TestCheckedRounds_IsChecked(t *testing.T) { + cr := newCheckedRounds(100, nil) + + for i := id.Round(0); i < 100; i += 2 { + cr.Check(i) + } + + for i := id.Round(0); i < 100; i++ { + if i%2 == 0 { + if !cr.IsChecked(i) { + t.Errorf("IsChecked() falsly reported round ID %d as not checked.", i) + } + } else if cr.IsChecked(i) { + t.Errorf("IsChecked() falsly reported round ID %d as checked.", i) + } + } +} + +// Happy path. +func Test_checkedRounds_Prune(t *testing.T) { + cr := newCheckedRounds(5, nil) + for i := id.Round(0); i < 10; i++ { + cr.Check(i) + } + + cr.Prune() + + if len(cr.m) != 5 || cr.l.Len() != 5 { + t.Errorf("Prune() did not remove the correct number of round IDs."+ + "\nexpected: %d\nmap: %d\nlist: %d", 5, + len(cr.m), cr.l.Len()) + } +} + +// Happy path: length of the list is not too long and does not need to be pruned. +func Test_checkedRounds_Prune_NoChange(t *testing.T) { + cr := newCheckedRounds(100, nil) + for i := id.Round(0); i < 10; i++ { + cr.Check(i) + } + + cr.Prune() + + if len(cr.m) != 10 || cr.l.Len() != 10 { + t.Errorf("Prune() did not remove the correct number of round IDs."+ + "\nexpected: %d\nmap: %d\nlist: %d", 5, + len(cr.m), cr.l.Len()) + } +} + +// Happy path. +func TestCheckedRounds_unmarshal(t *testing.T) { + expected := newCheckedRounds(100, nil) + rounds := make([][]byte, 10) + for i := id.Round(0); i < 10; i++ { + expected.Check(i) + rounds[i] = make([]byte, 8) + binary.LittleEndian.PutUint64(rounds[i], uint64(i)) + } + expected.recent = []id.Round{} + + cr := newCheckedRounds(100, nil) + cr.unmarshal(rounds) + + if !reflect.DeepEqual(expected, cr) { + t.Errorf("unmarshal() did not return the expected CheckedRounds."+ + "\nexpected: %+v\nreceived: %+v", expected, cr) + } +} diff --git a/storage/utility/blockStore.go b/storage/utility/blockStore.go new file mode 100644 index 0000000000000000000000000000000000000000..a405e9b966175b77f8a9803e2efbe72bf2cf27af --- /dev/null +++ b/storage/utility/blockStore.go @@ -0,0 +1,274 @@ +package utility + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/netTime" + "strconv" +) + +// Sizes in bytes +const ( + int64Size = 8 + marshalledSize = 4 * int64Size +) + +// Error messages +const ( + bsBuffLengthErr = "length of buffer %d != %d expected" + bsKvSaveErr = "failed to save blockStore to KV: %+v" + bsKvLoadErr = "failed to get BlockStore from storage: %+v" + bsKvUnmarshalErr = "failed to unmarshal BlockStore loaded from storage: %+v" + bJsonMarshalErr = "failed to JSON marshal block %d: %+v" + bKvSaveErr = "failed to save block %d to KV: %+v" + bKvDeleteErr = "failed to delete block %d from KV: %+v" + bKvLoadErr = "failed to get block %d from KV: %+v" + bJsonUnmarshalErr = "failed to JSON marshal block %d: %+v" +) + +// Storage keys and parts +const ( + delimiter = "/" + blockStoreKey = "blockStore" + blockStoreVersion = 0 + blockKey = "block" + blockVersion = 0 +) + +type Iterator interface { + Next() ([]byte, bool) +} + +type BlockStore struct { + block [][]byte + numBlocks int // The maximum number of blocks saved to the kv + blockSize int // The maximum number of items allowed in a block + firstSaved int // The index of the oldest block in the list + lastSaved int // The index of the newest block in the list + kv *versioned.KV +} + +// NewBlockStore returns a new BlockStore and saves it to storage. +func NewBlockStore(numBlocks, blockSize int, kv *versioned.KV) (*BlockStore, error) { + bs := &BlockStore{ + block: make([][]byte, 0, blockSize), + numBlocks: numBlocks, + blockSize: blockSize, + firstSaved: 0, + lastSaved: 0, + kv: kv, + } + + return bs, bs.save() +} + +// LoadBlockStore returns the BlockStore from storage and a concatenation of all +// blocks in storage. +func LoadBlockStore(kv *versioned.KV) (*BlockStore, [][]byte, error) { + bs := &BlockStore{kv: kv} + + // Get BlockStore parameters from storage + err := bs.load() + if err != nil { + return nil, nil, err + } + + // LoadBlockStore each block from storage and join together into single slice + var data, block [][]byte + for i := bs.firstSaved; i <= bs.lastSaved; i++ { + // Get the block from storage + block, err = bs.loadBlock(i) + if err != nil { + return nil, nil, err + } + + // Append block to the rest of the data + data = append(data, block...) + } + + // Save the last block into memory + bs.block = block + + return bs, data, nil +} + +// Store stores all items in the Iterator to storage in blocks. +func (bs *BlockStore) Store(iter Iterator) error { + // Iterate through all items in the Iterator and add each to the block in + // memory. When the block is full, it is saved to storage and a new block is + // added to until the iterator returns false. + for item, exists := iter.Next(); exists; item, exists = iter.Next() { + // If the block is full, save it to storage and start a new block + if len(bs.block) >= bs.blockSize { + if err := bs.saveBlock(); err != nil { + return err + } + + bs.lastSaved++ + bs.block = make([][]byte, 0, bs.blockSize) + } + + // Append the item to the block in memory + bs.block = append(bs.block, item) + } + + // Save the current partially filled block to storage + if err := bs.saveBlock(); err != nil { + return err + } + + // Calculate the new first saved index + oldFirstSaved := bs.firstSaved + if (bs.lastSaved+1)-bs.firstSaved > bs.numBlocks { + bs.firstSaved = (bs.lastSaved + 1) - bs.numBlocks + } + + // Save the BlockStorage parameters to storage + if err := bs.save(); err != nil { + return err + } + + // Delete all old blocks + bs.pruneBlocks(oldFirstSaved) + + return nil +} + +// saveBlock saves the block to an indexed storage. +func (bs *BlockStore) saveBlock() error { + // JSON marshal block + data, err := json.Marshal(bs.block) + if err != nil { + return errors.Errorf(bJsonMarshalErr, bs.lastSaved, err) + } + + // Construct versioning object + obj := versioned.Object{ + Version: blockVersion, + Timestamp: netTime.Now(), + Data: data, + } + + // Save to storage + err = bs.kv.Set(bs.getKey(bs.lastSaved), blockVersion, &obj) + if err != nil { + return errors.Errorf(bKvSaveErr, bs.lastSaved, err) + } + + return nil +} + +// loadBlock loads the block with the index from storage. +func (bs *BlockStore) loadBlock(i int) ([][]byte, error) { + // Get the data from the kv + obj, err := bs.kv.Get(bs.getKey(i), blockVersion) + if err != nil { + return nil, errors.Errorf(bKvLoadErr, i, err) + } + + // Unmarshal the block + var block [][]byte + err = json.Unmarshal(obj.Data, &block) + if err != nil { + return nil, errors.Errorf(bJsonUnmarshalErr, i, err) + } + + return block, nil +} + +// pruneBlocks reduces the number of saved blocks to numBlocks by removing the +// oldest blocks. +func (bs *BlockStore) pruneBlocks(firstSaved int) { + // Exit if no blocks need to be pruned + if (bs.lastSaved+1)-firstSaved < bs.numBlocks { + return + } + + // Delete all blocks before the firstSaved index + for ; firstSaved < bs.firstSaved; firstSaved++ { + err := bs.kv.Delete(bs.getKey(firstSaved), blockVersion) + if err != nil { + jww.WARN.Printf(bKvDeleteErr, bs.firstSaved, err) + } + } +} + +// getKey produces a block storage key for the given index. +func (bs *BlockStore) getKey(i int) string { + return blockKey + delimiter + strconv.Itoa(i) +} + +// save saves the parameters in BlockStore to storage. It does not save any +// block data. +func (bs *BlockStore) save() error { + // Construct versioning object + obj := versioned.Object{ + Version: blockStoreVersion, + Timestamp: netTime.Now(), + Data: bs.marshal(), + } + + // Save to storage + err := bs.kv.Set(blockStoreKey, blockStoreVersion, &obj) + if err != nil { + return errors.Errorf(bsKvSaveErr, err) + } + + return nil +} + +// load loads BlockStore parameters from storage. +func (bs *BlockStore) load() error { + // Get the data from the kv + obj, err := bs.kv.Get(blockStoreKey, blockStoreVersion) + if err != nil { + return errors.Errorf(bsKvLoadErr, err) + } + + // Unmarshal the data into a BlockStore + err = bs.unmarshal(obj.Data) + if err != nil { + return errors.Errorf(bsKvUnmarshalErr, err) + } + + return nil +} + +// marshal marshals the BlockStore integer values to a byte slice. +func (bs *BlockStore) marshal() []byte { + // Build list of values to store + values := []int{bs.numBlocks, bs.blockSize, bs.firstSaved, bs.lastSaved} + + // Convert each value to a byte slice and store + var buff bytes.Buffer + for _, val := range values { + b := make([]byte, int64Size) + binary.LittleEndian.PutUint64(b, uint64(val)) + buff.Write(b) + } + + // Return the bytes + return buff.Bytes() +} + +// unmarshal unmarshalls the BlockStore int values from the buffer. An error is +// returned if the length of the bytes is incorrect. +func (bs *BlockStore) unmarshal(b []byte) error { + // Return an error if the buffer is not the expected length + if len(b) != marshalledSize { + return errors.Errorf(bsBuffLengthErr, len(b), marshalledSize) + } + + // Convert the byte slices to ints and store + buff := bytes.NewBuffer(b) + bs.numBlocks = int(binary.LittleEndian.Uint64(buff.Next(int64Size))) + bs.blockSize = int(binary.LittleEndian.Uint64(buff.Next(int64Size))) + bs.firstSaved = int(binary.LittleEndian.Uint64(buff.Next(int64Size))) + bs.lastSaved = int(binary.LittleEndian.Uint64(buff.Next(int64Size))) + + return nil +} diff --git a/storage/utility/blockStore_test.go b/storage/utility/blockStore_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c9e2fb56daef0cff51eb7e283b463f9782ac4cf6 --- /dev/null +++ b/storage/utility/blockStore_test.go @@ -0,0 +1,414 @@ +package utility + +import ( + "bytes" + "encoding/binary" + "fmt" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/netTime" + "math/rand" + "reflect" + "strings" + "testing" +) + +type iter [][]byte + +func (it *iter) Next() ([]byte, bool) { + if len(*it) > 0 { + item := (*it)[0] + *it = (*it)[1:] + return item, true + } + + return nil, false +} + +// Happy path. +func TestNewBlockStore(t *testing.T) { + expected := &BlockStore{ + block: make([][]byte, 0, 20), + numBlocks: 50, + blockSize: 20, + firstSaved: 0, + lastSaved: 0, + kv: versioned.NewKV(make(ekv.Memstore)), + } + + bs, err := NewBlockStore(expected.numBlocks, expected.blockSize, expected.kv) + if err != nil { + t.Errorf("NewBlockStore() returned an error: %+v", err) + } + + if !reflect.DeepEqual(expected, bs) { + t.Errorf("NewBlockStore() did not return the expected BlockStore."+ + "\nexpected: %+v\nreceived: %+v", expected, bs) + } +} + +// Tests BlockStore storing and loading data in multiple situations. +func TestBlockStore_Store_LoadBlockStore(t *testing.T) { + values := []struct { + blockSize, numBlocks int + expectedFirstSaved, expectedLastSaved int + dataCutIndex int + }{ + {3, 5, 0, 3, 0}, // Multiple blocks, last block partial, no pruning + {10, 5, 0, 0, 0}, // Single block, last block full, no pruning + {15, 5, 0, 0, 0}, // Single block, last block partial, no pruning + + {2, 3, 2, 4, 4}, // Multiple blocks, last block partial, pruned + {5, 1, 1, 1, 5}, // Single block, last block full, pruned + {4, 1, 2, 2, 8}, // Single block, last block partial, pruned + } + + for i, v := range values { + // Create the initial data to store + iter := make(iter, 10) + for i := uint64(0); i < 10; i++ { + iter[i] = make([]byte, 8) + binary.LittleEndian.PutUint64(iter[i], i) + } + + // Calculate the expected data + expected := make([][]byte, len(iter[v.dataCutIndex:])) + copy(expected, iter[v.dataCutIndex:]) + + bs, err := NewBlockStore(v.numBlocks, v.blockSize, versioned.NewKV(make(ekv.Memstore))) + if err != nil { + t.Errorf("Failed to create new BlockStore (%d): %+v", i, err) + } + + // Attempt to store the data + err = bs.Store(&iter) + if err != nil { + t.Errorf("Store() returned an error (%d): %+v", i, err) + } + + if bs.firstSaved != v.expectedFirstSaved { + t.Errorf("Store() did not return the expected firstSaved (%d)."+ + "\nexpected: %d\nreceived: %d", i, v.expectedFirstSaved, bs.firstSaved) + } + + if bs.lastSaved != v.expectedLastSaved { + t.Errorf("Store() did not return the expected lastSaved (%d)."+ + "\nexpected: %d\nreceived: %d", i, v.expectedLastSaved, bs.lastSaved) + } + + // Attempt to load the data + loadBS, data, err := LoadBlockStore(bs.kv) + if err != nil { + t.Errorf("LoadBlockStore() returned an error (%d): %+v", i, err) + } + + // Check if the loaded BlockStore is correct + if !reflect.DeepEqual(bs, loadBS) { + t.Errorf("Loading wrong BlockStore from storage (%d)."+ + "\nexpected: %+v\nreceived: %+v", i, bs, loadBS) + } + + // Check if the loaded data is correct + if !reflect.DeepEqual(expected, data) { + t.Errorf("Loading wrong data from storage (%d)."+ + "\nexpected: %+v\nreceived: %+v", i, expected, data) + } + } +} + +// Tests that a block is successfully saved and loaded from storage. +func TestBlockStore_saveBlock_loadBlock(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + bs := &BlockStore{ + block: make([][]byte, 0, 20), + numBlocks: 50, + blockSize: 20, + firstSaved: 0, + lastSaved: 0, + kv: versioned.NewKV(make(ekv.Memstore)), + } + + for i := range bs.block { + bs.block[i] = make([]byte, 32) + prng.Read(bs.block[i]) + } + + err := bs.saveBlock() + if err != nil { + t.Errorf("saveBlock() returned an error: %+v", err) + } + + newBS := &BlockStore{kv: bs.kv} + block, err := newBS.loadBlock(0) + if err != nil { + t.Errorf("loadBlock() returned an error: %+v", err) + } + + if !reflect.DeepEqual(bs.block, block) { + t.Errorf("Failed to save and load block to storage."+ + "\nexpected: %+v\nreceived: %+v", bs.block, block) + } +} + +// Error path: failed to save to KV. +func TestBlockStore_saveBlock_SaveError(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + bs := &BlockStore{ + block: make([][]byte, 0, 20), + numBlocks: 50, + blockSize: 20, + firstSaved: 0, + lastSaved: 0, + kv: versioned.NewKV(make(ekv.Memstore)), + } + + for i := range bs.block { + bs.block[i] = make([]byte, 32) + prng.Read(bs.block[i]) + } + + err := bs.saveBlock() + if err != nil { + t.Errorf("saveBlock() returned an error: %+v", err) + } + + newBS := &BlockStore{kv: bs.kv} + block, err := newBS.loadBlock(0) + if err != nil { + t.Errorf("loadBlock() returned an error: %+v", err) + } + + if !reflect.DeepEqual(bs.block, block) { + t.Errorf("Failed to save and load block to storage."+ + "\nexpected: %+v\nreceived: %+v", bs.block, block) + } +} + +// Error path: loading of nonexistent key returns an error. +func TestBlockStore_loadBlock_LoadStorageError(t *testing.T) { + expectedErr := strings.SplitN(bKvLoadErr, "%", 2)[0] + bs := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + _, err := bs.loadBlock(0) + if err == nil || !strings.Contains(err.Error(), expectedErr) { + t.Errorf("loadBlock() did not return the expected error."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Error path: unmarshalling of invalid data fails. +func TestBlockStore_loadBlock_UnmarshalError(t *testing.T) { + bs := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + expectedErr := strings.SplitN(bJsonUnmarshalErr, "%", 2)[0] + + // Construct object with invalid data + obj := versioned.Object{ + Version: blockVersion, + Timestamp: netTime.Now(), + Data: []byte("invalid JSON"), + } + + // Save to storage + err := bs.kv.Set(bs.getKey(bs.lastSaved), blockVersion, &obj) + if err != nil { + t.Errorf("Failed to save data to KV: %+v", err) + } + + _, err = bs.loadBlock(0) + if err == nil || !strings.Contains(err.Error(), expectedErr) { + t.Errorf("loadBlock() did not return the expected error."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Happy path. +func TestBlockStore_pruneBlocks(t *testing.T) { + bs := &BlockStore{ + block: make([][]byte, 0, 32), + numBlocks: 5, + blockSize: 32, + firstSaved: 0, + lastSaved: 0, + kv: versioned.NewKV(make(ekv.Memstore)), + } + + // Save blocks to storage + for ; bs.lastSaved < 15; bs.lastSaved++ { + if err := bs.saveBlock(); err != nil { + t.Errorf("Failed to save block %d: %+v", bs.lastSaved, err) + } + } + + // Calculate the new first saved index + oldFirstSaved := bs.firstSaved + bs.firstSaved = bs.lastSaved - bs.numBlocks + + // Prune blocks + bs.pruneBlocks(oldFirstSaved) + + // Check that the old blocks were deleted + for i := 0; i < bs.lastSaved-bs.numBlocks; i++ { + if _, err := bs.kv.Get(bs.getKey(i), blockVersion); err == nil { + t.Errorf("pruneBlocks() failed to delete old block %d: %+v", i, err) + } + } + + // Check that the new blocks were not deleted + for i := bs.firstSaved; i < bs.lastSaved; i++ { + if _, err := bs.kv.Get(bs.getKey(i), blockVersion); err != nil { + t.Errorf("pruneBlocks() deleted block %d: %+v", i, err) + } + } + + // Call pruneBlocks when there are no blocks to prune + oldFirstSaved = bs.firstSaved + bs.firstSaved = bs.lastSaved - bs.numBlocks + bs.pruneBlocks(oldFirstSaved) + + // Check that the new blocks were not deleted + for i := bs.firstSaved; i < bs.lastSaved; i++ { + if _, err := bs.kv.Get(bs.getKey(i), blockVersion); err != nil { + t.Errorf("pruneBlocks() deleted block %d: %+v", i, err) + } + } +} + +// Consistency test. +func TestBlockStore_getKey_Consistency(t *testing.T) { + expectedKeys := []string{ + "block/0", "block/1", "block/2", "block/3", "block/4", + "block/5", "block/6", "block/7", "block/8", "block/9", + } + var bs BlockStore + + for i, expected := range expectedKeys { + key := bs.getKey(i) + if key != expected { + t.Errorf("getKey did not return the correct key for the index %d."+ + "\nexpected: %s\nreceived: %s", i, expected, key) + } + } +} + +// Tests that a BlockStore can be saved and loaded from the KV correctly. +func TestBlockStore_save_load(t *testing.T) { + bs := &BlockStore{ + numBlocks: 5, blockSize: 6, firstSaved: 7, lastSaved: 8, + kv: versioned.NewKV(make(ekv.Memstore)), + } + + err := bs.save() + if err != nil { + t.Errorf("save() returned an error: %+v", err) + } + + testBS := &BlockStore{kv: bs.kv} + err = testBS.load() + if err != nil { + t.Errorf("load() returned an error: %+v", err) + } + + if !reflect.DeepEqual(bs, testBS) { + t.Errorf("Failed to save and load BlockStore to KV."+ + "\nexpected: %+v\nreceived: %+v", bs, testBS) + } +} + +// Error path: loading of unsaved BlockStore fails. +func TestBlockStore_load_KvGetError(t *testing.T) { + expectedErr := strings.SplitN(bsKvLoadErr, "%", 2)[0] + + testBS := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + err := testBS.load() + if err == nil || !strings.Contains(err.Error(), expectedErr) { + t.Errorf("load() did not return an error for a nonexistent item in storage."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Error path: unmarshalling of invalid data fails. +func TestBlockStore_load_UnmarshalError(t *testing.T) { + expectedErr := strings.SplitN(bsKvUnmarshalErr, "%", 2)[0] + kv := versioned.NewKV(make(ekv.Memstore)) + + // Construct invalid versioning object + obj := versioned.Object{ + Version: blockStoreVersion, + Timestamp: netTime.Now(), + Data: []byte("invalid data"), + } + + // Save to storage + err := kv.Set(blockStoreKey, blockStoreVersion, &obj) + if err != nil { + t.Fatalf("failed to save object to storage: %+v", err) + } + + // Try to retrieve invalid object + testBS := &BlockStore{kv: kv} + err = testBS.load() + if err == nil || !strings.Contains(err.Error(), expectedErr) { + t.Errorf("load() did not return an error for a nonexistent item in storage."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Consistency test. +func TestBlockStore_unmarshal(t *testing.T) { + buff := []byte{5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0} + expected := &BlockStore{numBlocks: 5, blockSize: 6, firstSaved: 7, lastSaved: 8} + + bs := &BlockStore{} + err := bs.unmarshal(buff) + if err != nil { + t.Errorf("unmarshal() returned an error: %+v", err) + } + + if !reflect.DeepEqual(expected, bs) { + t.Errorf("unmarshal() did not return the expected BlockStore."+ + "\nexpected: %+v\nreceived: %+v", expected, bs) + } +} + +// Error path: length of buffer incorrect. +func TestBlockStore_unmarshal_BuffLengthError(t *testing.T) { + expectedErr := fmt.Sprintf(bsBuffLengthErr, 0, marshalledSize) + bs := BlockStore{} + err := bs.unmarshal([]byte{}) + if err == nil || err.Error() != expectedErr { + t.Errorf("unmarshal() did not return the expected error for a buffer "+ + "of the wrong size.\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Consistency test. +func TestBlockStore_marshal(t *testing.T) { + expected := []byte{5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0} + bs := &BlockStore{numBlocks: 5, blockSize: 6, firstSaved: 7, lastSaved: 8} + + buff := bs.marshal() + + if !bytes.Equal(expected, buff) { + t.Errorf("marshal() did not return the expected bytes."+ + "\nexpected: %+v\nreceived: %+v", expected, buff) + } +} + +// Tests that marshal and unmarshal work together. +func TestBlockStore_marshal_unmarshal(t *testing.T) { + bs := &BlockStore{numBlocks: 5, blockSize: 6, firstSaved: 7, lastSaved: 8} + + buff := bs.marshal() + + testBS := &BlockStore{} + err := testBS.unmarshal(buff) + if err != nil { + t.Errorf("unmarshal() returned an error: %+v", err) + } + + if !reflect.DeepEqual(bs, testBS) { + t.Errorf("failed to marshal and unmarshal BlockStore."+ + "\nexpected: %+v\nreceived: %+v", bs, testBS) + } +}