diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c4dc10de3ec54dfff8c212fcf76c2e6aac6965aa..11649fc9a614ca8b5734552680536738e675fecb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,8 +8,9 @@ before_script: - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan -t rsa $GITLAB_SERVER > ~/.ssh/known_hosts + - rm -rf ~/.gitconfig - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://gitlab.com/" - - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://git.xx.network/" + - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://git.xx.network/" --add - export PATH=$HOME/go/bin:$PATH stages: @@ -51,6 +52,7 @@ build: except: - tags script: + - make version - mkdir -p release # - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./... - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.linux64 main.go @@ -80,6 +82,7 @@ bindings-ios: - ios script: - go get -u golang.org/x/mobile/cmd/gomobile@76c259c465ba39f84de7e2751a666612ddca556b + - gomobile init - gomobile bind -target ios gitlab.com/elixxir/client/bindings - ls - zip -r iOS.zip Bindings.xcframework @@ -97,6 +100,7 @@ bindings-android: - export ANDROID_HOME=/android-sdk - export PATH=$PATH:/android-sdk/platform-tools/:/usr/local/go/bin - go get -u golang.org/x/mobile/cmd/gomobile + - gomobile init - gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings artifacts: paths: diff --git a/README.md b/README.md index ebf490f59a5ab4ac6c8a154785443cff0ed28310..a5041e876b23c787616d2f260830f2aca19de0d3 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,9 @@ Flags: --unsafe-channel-creation Turns off the user identity authenticated channel check, automatically approving authenticated channels + --verboseRoundTracking Verbose round tracking, keeps track and prints + all rounds the client was aware of while running. + Defaults to false if not set. -v, --logLevel uint Level of debugging to print (0 = info, 1 = debug, >1 = trace). (Default info) --waitTimeout uint The number of seconds to wait for messages to diff --git a/api/client.go b/api/client.go index b1edca2841c9c349c1443cd6e1f903530265963b..012e980a5d946c5dd632b896a9d26037d9b308bd 100644 --- a/api/client.go +++ b/api/client.go @@ -75,7 +75,7 @@ type Client struct { func NewClient(ndfJSON, storageDir string, password []byte, registrationCode string) error { jww.INFO.Printf("NewClient(dir: %s)", storageDir) // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) // Parse the NDF def, err := parseNDF(ndfJSON) @@ -106,7 +106,7 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password []byte) error { jww.INFO.Printf("NewPrecannedClient()") // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) rngStream := rngStreamGen.GetStream() // Parse the NDF @@ -135,7 +135,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ 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) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) rngStream := rngStreamGen.GetStream() // Parse the NDF @@ -161,7 +161,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCo func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) { jww.INFO.Printf("OpenClient()") // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) // Get current client version currentVersion, err := version.ParseVersion(SEMVER) @@ -611,10 +611,20 @@ func (c *Client) GetPreferredBins(countryCode string) ([]string, error) { // Add additional bins in special cases switch bin { - case region.Africa: - bins = append(bins, region.WesternEurope.String()) + case region.SouthAndCentralAmerica: + bins = append(bins, region.NorthAmerica.String()) case region.MiddleEast: - bins = append(bins, region.EasternEurope.String()) + bins = append(bins, region.EasternEurope.String(), region.CentralEurope.String(), region.WesternAsia.String()) + case region.NorthernAfrica: + bins = append(bins, region.WesternEurope.String(), region.CentralEurope.String()) + case region.SouthernAfrica: + bins = append(bins, region.WesternEurope.String(), region.CentralEurope.String()) + case region.EasternAsia: + bins = append(bins, region.WesternAsia.String(), region.Oceania.String(), region.NorthAmerica.String()) + case region.WesternAsia: + bins = append(bins, region.EasternAsia.String(), region.Russia.String(), region.MiddleEast.String()) + case region.Oceania: + bins = append(bins, region.EasternAsia.String(), region.NorthAmerica.String()) } return bins, nil diff --git a/api/mnemonic.go b/api/mnemonic.go index fd3ce7259be248692591a48bc86f745fd0089e7c..9b262da82c81bebcc01fd185adbc5d60a196cb6b 100644 --- a/api/mnemonic.go +++ b/api/mnemonic.go @@ -10,10 +10,10 @@ package api import ( "github.com/pkg/errors" "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/chacha" "gitlab.com/xx_network/crypto/csprng" xxMnemonic "gitlab.com/xx_network/crypto/mnemonic" "gitlab.com/xx_network/primitives/utils" - "golang.org/x/crypto/chacha20poly1305" "path/filepath" "strings" ) @@ -24,7 +24,7 @@ const mnemonicFile = ".recovery" // This encrypted data saved in storage. func StoreSecretWithMnemonic(secret []byte, path string) (string, error) { // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG).GetStream() + rng := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG).GetStream() // Ensure path is appended by filepath separator "/" if !strings.HasSuffix(path, string(filepath.Separator)) { @@ -44,7 +44,7 @@ func StoreSecretWithMnemonic(secret []byte, path string) (string, error) { } // Encrypt secret with mnemonic as key - ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, rng) + ciphertext, err := chacha.Encrypt(decodedMnemonic, secret, rng) if err != nil { return "", errors.Errorf("Failed to encrypt secret with mnemonic: %v", err) } @@ -87,47 +87,10 @@ func LoadSecretWithMnemonic(mnemonic, path string) (secret []byte, err error) { } // Decrypt the stored secret - secret, err = decryptWithMnemonic(data, decodedMnemonic) + secret, err = chacha.Decrypt(decodedMnemonic, data) if err != nil { return nil, errors.Errorf("Failed to decrypt secret: %v", err) } return secret, nil } - -// encryptWithMnemonic is a helper function which encrypts the given secret -// using the mnemonic as the key. -func encryptWithMnemonic(data, decodedMnemonic []byte, - rng csprng.Source) (ciphertext []byte, error error) { - chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:]) - if err != nil { - return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err) - } - - // Generate the nonce - nonce := make([]byte, chaCipher.NonceSize()) - nonce, err = csprng.Generate(chaCipher.NonceSize(), rng) - if err != nil { - return nil, errors.Errorf("Failed to generate nonce: %v", err) - } - - ciphertext = chaCipher.Seal(nonce, nonce, data, nil) - return ciphertext, nil -} - -// decryptWithMnemonic is a helper function which decrypts the secret -// from storage, using the mnemonic as the key. -func decryptWithMnemonic(data, decodedMnemonic []byte) ([]byte, error) { - chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:]) - if err != nil { - return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err) - } - - nonceLen := chaCipher.NonceSize() - nonce, ciphertext := data[:nonceLen], data[nonceLen:] - plaintext, err := chaCipher.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, errors.Wrap(err, "Cannot decrypt with password!") - } - return plaintext, nil -} diff --git a/api/mnemonic_test.go b/api/mnemonic_test.go index 66b2f13dfc8e3f297be051f1f58206c94d22c399..a56a73b1f524018f686af392478f89b7dc2d1dc9 100644 --- a/api/mnemonic_test.go +++ b/api/mnemonic_test.go @@ -38,41 +38,6 @@ func TestStoreSecretWithMnemonic(t *testing.T) { } -func TestEncryptDecryptMnemonic(t *testing.T) { - prng := NewPrng(32) - - // Generate a test mnemonic - testMnemonic, err := xxMnemonic.GenerateMnemonic(prng, 32) - if err != nil { - t.Fatalf("GenerateMnemonic error: %v", err) - } - - decodedMnemonic, err := xxMnemonic.DecodeMnemonic(testMnemonic) - if err != nil { - t.Fatalf("DecodeMnemonic error: %v", err) - } - - secret := []byte("test123") - - // Encrypt the secret - ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, prng) - if err != nil { - t.Fatalf("encryptWithMnemonic error: %v", err) - } - - // Decrypt the secret - received, err := decryptWithMnemonic(ciphertext, decodedMnemonic) - if err != nil { - t.Fatalf("decryptWithMnemonic error: %v", err) - } - - // Test if secret matches decrypted data - if !bytes.Equal(received, secret) { - t.Fatalf("Decrypted data does not match original plaintext."+ - "\n\tExpected: %v\n\tReceived: %v", secret, received) - } -} - func TestLoadSecretWithMnemonic(t *testing.T) { secret := []byte("test123") storageDir := "ignore.1" diff --git a/api/utilsInterfaces_test.go b/api/utilsInterfaces_test.go index 0997993aebfabf2a0bd4fa4ca2efb9c269112a1f..380a691a4b21e23534b28a836ca8c30cf475afdf 100644 --- a/api/utilsInterfaces_test.go +++ b/api/utilsInterfaces_test.go @@ -100,6 +100,11 @@ func (t *testNetworkManagerGeneric) Follow(report interfaces.ClientErrorReport) func (t *testNetworkManagerGeneric) CheckGarbledMessages() { return } + +func (t *testNetworkManagerGeneric) GetVerboseRounds() string { + return "" +} + func (t *testNetworkManagerGeneric) SendE2E(message.Send, params.E2E, *stoppable.Single) ( []id.Round, cE2e.MessageID, time.Time, error) { rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)} diff --git a/api/utils_test.go b/api/utils_test.go index a20f1e0bf64a0da34935d467ac483caccd742dce..cff0d76979ea2a75e9d8db400de68bcc6e0f00c8 100644 --- a/api/utils_test.go +++ b/api/utils_test.go @@ -94,6 +94,7 @@ func getNDF(face interface{}) *ndf.NetworkDefinition { ID: nodeID.Bytes(), Address: "", TlsCertificate: string(cert), + Status: ndf.Active, }, }, Gateways: []ndf.Gateway{ diff --git a/api/version_vars.go b/api/version_vars.go index 4c7400d408c23d6a07c021ca80643bc7b362fc22..fbf2295851c49dc762e377b4b311e45e270d984d 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-07-27 14:13:01.428348 -0500 CDT m=+0.036560333 +// 2021-10-07 12:02:04.067615 -0500 CDT m=+0.027463409 package api -const GITVERSION = `758d1e91 Merge branch 'protoMainNet' into 'release'` -const SEMVER = "2.8.0" +const GITVERSION = `86c66226 update deps` +const SEMVER = "2.10.0" const DEPENDENCIES = `module gitlab.com/elixxir/client go 1.13 @@ -24,16 +24,17 @@ require ( 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.20210714201329-5efcbdfac3ca - gitlab.com/elixxir/crypto v0.0.7-0.20210714201100-45fb778a00fb + gitlab.com/elixxir/comms v0.0.4-0.20211006231929-cc2735ca43a6 + gitlab.com/elixxir/crypto v0.0.7-0.20211006231624-44434504fff4 gitlab.com/elixxir/ekv v0.1.5 - gitlab.com/elixxir/primitives v0.0.3-0.20210714200942-a908050c230c - gitlab.com/xx_network/comms v0.0.4-0.20210714165756-8e3b40d71db1 - gitlab.com/xx_network/crypto v0.0.5-0.20210714165656-1ed326047ba9 - gitlab.com/xx_network/primitives v0.0.4-0.20210727175935-dd746a0d73de + gitlab.com/elixxir/primitives v0.0.3-0.20210920180121-b85bca5212f4 + gitlab.com/xx_network/comms v0.0.4-0.20211006231434-99dd38f025a7 + gitlab.com/xx_network/crypto v0.0.5-0.20211006222352-8e0ac37b86b0 + gitlab.com/xx_network/primitives v0.0.4-0.20210915220237-70cb4551d6f3 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect + google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/bindings/client.go b/bindings/client.go index f6fc9ef358c1a2000767f11d9f712f7ecd22fbc5..91293ba3d20f0b98a68f4691e3cb0e2ea589bdcd 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -22,6 +22,7 @@ import ( "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" + "google.golang.org/grpc/grpclog" "strings" "sync" "time" @@ -161,6 +162,13 @@ func RegisterLogWriter(writer LogWriter) { jww.SetLogOutput(&writerAdapter{lw: writer}) } +// EnableGrpcLogs sets GRPC trace logging +func EnableGrpcLogs(writer LogWriter) { + logger := &writerAdapter{lw: writer} + grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity( + logger, logger, logger, 99)) +} + //Unmarshals a marshaled contact object, returns an error if it fails func UnmarshalContact(b []byte) (*Contact, error) { c, err := contact.Unmarshal(b) diff --git a/bindings/event.go b/bindings/event.go index 7e28c72e025768a7dc454098e070d923c9832f2f..4f5aeacf907edfb09f23662e986607c9d820633c 100644 --- a/bindings/event.go +++ b/bindings/event.go @@ -7,16 +7,18 @@ package bindings -import ( - "gitlab.com/elixxir/client/interfaces" -) +// EventCallbackFunctionObject bindings interface which contains function +// that implements the EventCallbackFunction +type EventCallbackFunctionObject interface { + ReportEvent(priority int, category, evtType, details string) +} // RegisterEventCallback records the given function to receive // ReportableEvent objects. It returns the internal index // of the callback so that it can be deleted later. func (c *Client) RegisterEventCallback(name string, - myFunc interfaces.EventCallbackFunction) error { - return c.api.RegisterEventCallback(name, myFunc) + myObj EventCallbackFunctionObject) error { + return c.api.RegisterEventCallback(name, myObj.ReportEvent) } // UnregisterEventCallback deletes the callback identified by the diff --git a/bindings/group.go b/bindings/group.go index 00c45668e08563488884f68e5c9f76b2a0907687..2df75cdd1519618135db887d28cef16d14a12b29 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -23,66 +23,66 @@ type GroupChat struct { // GroupRequestFunc contains a function callback that is called when a group // request is received. type GroupRequestFunc interface { - GroupRequestCallback(g Group) + GroupRequestCallback(g *Group) } // GroupReceiveFunc contains a function callback that is called when a group // message is received. type GroupReceiveFunc interface { - GroupReceiveCallback(msg GroupMessageReceive) + GroupReceiveCallback(msg *GroupMessageReceive) } // NewGroupManager creates a new group chat manager. func NewGroupManager(client *Client, requestFunc GroupRequestFunc, - receiveFunc GroupReceiveFunc) (GroupChat, error) { + receiveFunc GroupReceiveFunc) (*GroupChat, error) { requestCallback := func(g gs.Group) { - requestFunc.GroupRequestCallback(Group{g}) + requestFunc.GroupRequestCallback(&Group{g}) } receiveCallback := func(msg gc.MessageReceive) { - receiveFunc.GroupReceiveCallback(GroupMessageReceive{msg}) + receiveFunc.GroupReceiveCallback(&GroupMessageReceive{msg}) } // Create a new group chat manager m, err := gc.NewManager(&client.api, requestCallback, receiveCallback) if err != nil { - return GroupChat{}, err + return nil, err } // Start group request and message retrieval workers err = client.api.AddService(m.StartProcesses) if err != nil { - return GroupChat{}, err + return nil, err } - return GroupChat{m}, nil + return &GroupChat{m}, nil } // MakeGroup creates a new group and sends a group request to all members in the // group. The ID of the new group, the rounds the requests were sent on, and the // status of the send are contained in NewGroupReport. -func (g GroupChat) MakeGroup(membership IdList, name, message []byte) (NewGroupReport, error) { +func (g *GroupChat) MakeGroup(membership *IdList, name, message []byte) (*NewGroupReport, error) { grp, rounds, status, err := g.m.MakeGroup(membership.list, name, message) - return NewGroupReport{Group{grp}, rounds, status}, err + return &NewGroupReport{&Group{grp}, rounds, status}, err } // ResendRequest resends a group request to all members in the group. The rounds // they were sent on and the status of the send are contained in NewGroupReport. -func (g GroupChat) ResendRequest(groupIdBytes []byte) (NewGroupReport, error) { +func (g *GroupChat) ResendRequest(groupIdBytes []byte) (*NewGroupReport, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return NewGroupReport{}, + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } rounds, status, err := g.m.ResendRequest(groupID) - return NewGroupReport{Group{}, rounds, status}, nil + return &NewGroupReport{&Group{}, rounds, status}, nil } // JoinGroup allows a user to join a group when they receive a request. The // caller must pass in the serialized bytes of a Group. -func (g GroupChat) JoinGroup(serializedGroupData []byte) error { +func (g *GroupChat) JoinGroup(serializedGroupData []byte) error { grp, err := gs.DeserializeGroup(serializedGroupData) if err != nil { return err @@ -91,7 +91,7 @@ func (g GroupChat) JoinGroup(serializedGroupData []byte) error { } // LeaveGroup deletes a group so a user no longer has access. -func (g GroupChat) LeaveGroup(groupIdBytes []byte) error { +func (g *GroupChat) LeaveGroup(groupIdBytes []byte) error { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { return errors.Errorf("Failed to unmarshal group ID: %+v", err) @@ -102,7 +102,7 @@ func (g GroupChat) LeaveGroup(groupIdBytes []byte) error { // Send sends the message to the specified group. Returns the round the messages // were sent on. -func (g GroupChat) Send(groupIdBytes, message []byte) (int64, error) { +func (g *GroupChat) Send(groupIdBytes, message []byte) (int64, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { return 0, errors.Errorf("Failed to unmarshal group ID: %+v", err) @@ -114,28 +114,28 @@ func (g GroupChat) Send(groupIdBytes, message []byte) (int64, error) { // GetGroups returns an IdList containing a list of group IDs that the user is a // part of. -func (g GroupChat) GetGroups() IdList { - return IdList{g.m.GetGroups()} +func (g *GroupChat) GetGroups() *IdList { + return &IdList{g.m.GetGroups()} } // GetGroup returns the group with the group ID. If no group exists, then the // error "failed to find group" is returned. -func (g GroupChat) GetGroup(groupIdBytes []byte) (Group, error) { +func (g *GroupChat) GetGroup(groupIdBytes []byte) (*Group, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return Group{}, errors.Errorf("Failed to unmarshal group ID: %+v", err) + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } grp, exists := g.m.GetGroup(groupID) if !exists { - return Group{}, errors.New("failed to find group") + return nil, errors.New("failed to find group") } - return Group{grp}, nil + return &Group{grp}, nil } // NumGroups returns the number of groups the user is a part of. -func (g GroupChat) NumGroups() int { +func (g *GroupChat) NumGroups() int { return g.m.NumGroups() } @@ -143,20 +143,20 @@ func (g GroupChat) NumGroups() int { // the group, a list of rounds that the group requests were sent on, and the // status of the send. type NewGroupReport struct { - group Group + group *Group rounds []id.Round status gc.RequestStatus } // GetGroup returns the Group. -func (ngr NewGroupReport) GetGroup() Group { +func (ngr *NewGroupReport) GetGroup() *Group { return ngr.group } // GetRoundList returns the RoundList containing a list of rounds requests were // sent on. -func (ngr NewGroupReport) GetRoundList() RoundList { - return RoundList{ngr.rounds} +func (ngr *NewGroupReport) GetRoundList() *RoundList { + return &RoundList{ngr.rounds} } // GetStatus returns the status of the requests sent when creating a new group. @@ -164,7 +164,7 @@ func (ngr NewGroupReport) GetRoundList() RoundList { // 1 all requests failed to send // 2 some request failed and some succeeded // 3, all requests sent successfully -func (ngr NewGroupReport) GetStatus() int { +func (ngr *NewGroupReport) GetStatus() int { return int(ngr.status) } @@ -179,24 +179,24 @@ type Group struct { } // GetName returns the name set by the user for the group. -func (g Group) GetName() []byte { +func (g *Group) GetName() []byte { return g.g.Name } // GetID return the 33-byte unique group ID. -func (g Group) GetID() []byte { +func (g *Group) GetID() []byte { return g.g.ID.Bytes() } // GetMembership returns a list of contacts, one for each member in the group. // The list is in order; the first contact is the leader/creator of the group. // All subsequent members are ordered by their ID. -func (g Group) GetMembership() GroupMembership { - return GroupMembership{g.g.Members} +func (g *Group) GetMembership() *GroupMembership { + return &GroupMembership{g.g.Members} } // Serialize serializes the Group. -func (g Group) Serialize() []byte { +func (g *Group) Serialize() []byte { return g.g.Serialize() } @@ -211,18 +211,18 @@ type GroupMembership struct { } // Len returns the number of members in the group membership. -func (gm GroupMembership) Len() int { - return gm.Len() +func (gm *GroupMembership) Len() int { + return len(gm.m) } // Get returns the member at the index. The member at index 0 is always the // group leader. An error is returned if the index is out of range. -func (gm GroupMembership) Get(i int) (GroupMember, error) { - if i < 0 || i > gm.Len() { - return GroupMember{}, errors.Errorf("ID list index must be between %d "+ +func (gm *GroupMembership) Get(i int) (*GroupMember, error) { + if i < 0 || i >= gm.Len() { + return nil, errors.Errorf("ID list index must be between %d "+ "and the last element %d.", 0, gm.Len()) } - return GroupMember{gm.m[i]}, nil + return &GroupMember{gm.m[i]}, nil } //// @@ -240,7 +240,7 @@ func (gm GroupMember) GetID() []byte { // GetDhKey returns the byte representation of the public Diffie–Hellman key of // the member. -func (gm GroupMember) GetDhKey() []byte { +func (gm *GroupMember) GetDhKey() []byte { return gm.DhKey.Bytes() } @@ -255,47 +255,58 @@ type GroupMessageReceive struct { } // GetGroupID returns the 33-byte group ID. -func (gmr GroupMessageReceive) GetGroupID() []byte { +func (gmr *GroupMessageReceive) GetGroupID() []byte { return gmr.GroupID.Bytes() } // GetMessageID returns the message ID. -func (gmr GroupMessageReceive) GetMessageID() []byte { +func (gmr *GroupMessageReceive) GetMessageID() []byte { return gmr.ID.Bytes() } // GetPayload returns the message payload. -func (gmr GroupMessageReceive) GetPayload() []byte { +func (gmr *GroupMessageReceive) GetPayload() []byte { return gmr.Payload } // GetSenderID returns the 33-byte user ID of the sender. -func (gmr GroupMessageReceive) GetSenderID() []byte { +func (gmr *GroupMessageReceive) GetSenderID() []byte { return gmr.SenderID.Bytes() } // GetRecipientID returns the 33-byte user ID of the recipient. -func (gmr GroupMessageReceive) GetRecipientID() []byte { +func (gmr *GroupMessageReceive) GetRecipientID() []byte { return gmr.RecipientID.Bytes() } // GetEphemeralID returns the ephemeral ID of the recipient. -func (gmr GroupMessageReceive) GetEphemeralID() int64 { +func (gmr *GroupMessageReceive) GetEphemeralID() int64 { return gmr.EphemeralID.Int64() } // GetTimestampNano returns the message timestamp in nanoseconds. -func (gmr GroupMessageReceive) GetTimestampNano() int64 { +func (gmr *GroupMessageReceive) GetTimestampNano() int64 { return gmr.Timestamp.UnixNano() } +// GetTimestampMS returns the message timestamp in milliseconds. +func (gmr *GroupMessageReceive) GetTimestampMS() int64 { + return gmr.Timestamp.UnixNano() / 1_000_000 +} + // GetRoundID returns the ID of the round the message was sent on. -func (gmr GroupMessageReceive) GetRoundID() int64 { +func (gmr *GroupMessageReceive) GetRoundID() int64 { return int64(gmr.RoundID) } // GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the // message was sent on. -func (gmr GroupMessageReceive) GetRoundTimestampNano() int64 { +func (gmr *GroupMessageReceive) GetRoundTimestampNano() int64 { return gmr.RoundTimestamp.UnixNano() } + +// GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the +// message was sent on. +func (gmr *GroupMessageReceive) GetRoundTimestampMS() int64 { + return gmr.RoundTimestamp.UnixNano() / 1_000_000 +} diff --git a/bindings/jsons.go b/bindings/jsons.go new file mode 100644 index 0000000000000000000000000000000000000000..5405baf8bd9f74c63d236e913043be576479adf8 --- /dev/null +++ b/bindings/jsons.go @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2021 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "io/ioutil" + "net/http" +) + +// Returns a []byte containing the JSON data describing client errors. +// See https://git.xx.network/elixxir/client-error-database/ +func DownloadErrorDB() ([]byte, error) { + // Build a request for the file + resp, err := http.Get("https://elixxir-bins.s3-us-west-1.amazonaws.com/client/errors/clientErrors.json") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Download the contents of the file + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // Return it to the user + return content, nil +} + +// Returns a []byte containing the JSON data describing registered dApps. +// See https://git.xx.network/elixxir/registered-dapps +func DownloadDAppRegistrationDB() ([]byte, error) { + // Build a request for the file + resp, err := http.Get("https://elixxir-bins.s3-us-west-1.amazonaws.com/client/dapps/appdb.json") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Download the contents of the file + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // Return it to the user + return content, nil +} diff --git a/bindings/jsons_test.go b/bindings/jsons_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a4405fd47fcd0908c0b8acff7a234b7732778e0f --- /dev/null +++ b/bindings/jsons_test.go @@ -0,0 +1,29 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2021 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "fmt" + "testing" +) + +func TestDownloadErrorDB(t *testing.T) { + json, err := DownloadErrorDB() + if err != nil { + t.Errorf("DownloadErrorDB returned error: %s", err) + } + fmt.Printf("json: %s\n", string(json)) +} + +func TestDownloadDAppRegistrationDB(t *testing.T) { + json, err := DownloadDAppRegistrationDB() + if err != nil { + t.Errorf("DownloadDAppRegistrationDB returned error: %s", err) + } + fmt.Printf("json: %s\n", string(json)) +} diff --git a/bindings/list.go b/bindings/list.go index a97df24f299d994380d46de8ae9971ad9133c1e7..1331c378e2b2f31c457ef2f3d082c51e186f2fbb 100644 --- a/bindings/list.go +++ b/bindings/list.go @@ -123,17 +123,17 @@ type IdList struct { } // MakeIdList creates a new empty IdList. -func MakeIdList() IdList { - return IdList{[]*id.ID{}} +func MakeIdList() *IdList { + return &IdList{[]*id.ID{}} } // Len returns the number of IDs in the list. -func (idl IdList) Len() int { +func (idl *IdList) Len() int { return len(idl.list) } // Add appends the ID bytes to the end of the list. -func (idl IdList) Add(idBytes []byte) error { +func (idl *IdList) Add(idBytes []byte) error { newID, err := id.Unmarshal(idBytes) if err != nil { return err @@ -145,7 +145,7 @@ func (idl IdList) Add(idBytes []byte) error { // Get returns the ID at the index. An error is returned if the index is out of // range. -func (idl IdList) Get(i int) ([]byte, error) { +func (idl *IdList) Get(i int) ([]byte, error) { if i < 0 || i > len(idl.list) { return nil, errors.Errorf("ID list index must be between %d and the "+ "last element %d.", 0, len(idl.list)) diff --git a/bindings/version.go b/bindings/version.go new file mode 100644 index 0000000000000000000000000000000000000000..ec517de07f5d262e8e761e85e39bcd68d83914b9 --- /dev/null +++ b/bindings/version.go @@ -0,0 +1,18 @@ +package bindings + +import "gitlab.com/elixxir/client/api" + +// GetVersion returns the api SEMVER +func GetVersion() string { + return api.SEMVER +} + +// GetGitVersion rturns the api GITVERSION +func GetGitVersion() string { + return api.GITVERSION +} + +// GetDependencies returns the api DEPENDENCIES +func GetDependencies() string { + return api.DEPENDENCIES +} diff --git a/cmd/root.go b/cmd/root.go index 41cd73e10c7b68e224776771c31e8cfb1323fbe7..036ff66cf9fef36c45b79b114ce85a5de5774b59 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,6 +12,7 @@ import ( "encoding/base64" "encoding/binary" "encoding/hex" + "errors" "fmt" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" @@ -28,6 +29,7 @@ import ( "runtime/pprof" "strconv" "strings" + "sync" "time" ) @@ -186,53 +188,63 @@ var rootCmd = &cobra.Command{ } paramsE2E := params.GetDefaultE2E() paramsUnsafe := params.GetDefaultUnsafe() - + wg := &sync.WaitGroup{} sendCnt := int(viper.GetUint("sendCount")) - sendDelay := time.Duration(viper.GetUint("sendDelay")) - for i := 0; i < sendCnt; i++ { - fmt.Printf("Sending to %s: %s\n", recipientID, msgBody) - var roundIDs []id.Round - var roundTimeout time.Duration - if unsafe { - roundIDs, err = client.SendUnsafe(msg, - paramsUnsafe) - roundTimeout = paramsUnsafe.Timeout - } else { - roundIDs, _, _, err = client.SendE2E(msg, - paramsE2E) - roundTimeout = paramsE2E.Timeout - } - if err != nil { - jww.FATAL.Panicf("%+v", err) - } - - // Construct the callback function which prints out the rounds' results - f := func(allRoundsSucceeded, timedOut bool, - rounds map[id.Round]api.RoundResult) { - printRoundResults(allRoundsSucceeded, timedOut, rounds, roundIDs, msg) - } - - // Have the client report back the round results - err = client.GetRoundResults(roundIDs, roundTimeout, f) - if err != nil { - jww.FATAL.Panicf("%+v", err) + wg.Add(sendCnt) + go func() { + //sendDelay := time.Duration(viper.GetUint("sendDelay")) + for i := 0; i < sendCnt; i++ { + go func(i int) { + defer wg.Done() + fmt.Printf("Sending to %s: %s\n", recipientID, msgBody) + var roundIDs []id.Round + var roundTimeout time.Duration + if unsafe { + roundIDs, err = client.SendUnsafe(msg, + paramsUnsafe) + roundTimeout = paramsUnsafe.Timeout + } else { + roundIDs, _, _, err = client.SendE2E(msg, + paramsE2E) + roundTimeout = paramsE2E.Timeout + } + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + + // Construct the callback function which prints out the rounds' results + f := func(allRoundsSucceeded, timedOut bool, + rounds map[id.Round]api.RoundResult) { + printRoundResults(allRoundsSucceeded, timedOut, rounds, roundIDs, msg) + } + + // Have the client report back the round results + err = errors.New("derp") + for j := 0; j < 5 && err != nil; j++ { + err = client.GetRoundResults(roundIDs, roundTimeout, f) + } + + if err != nil { + jww.FATAL.Panicf("Message sending for send %d failed: %+v", i, err) + } + }(i) } - - time.Sleep(sendDelay * time.Millisecond) - } + }() // Wait until message timeout or we receive enough then exit // TODO: Actually check for how many messages we've received expectedCnt := viper.GetUint("receiveCount") receiveCnt := uint(0) waitSecs := viper.GetUint("waitTimeout") - waitTimeout := time.Duration(waitSecs) + waitTimeout := time.Duration(waitSecs)* time.Second done := false + for !done && expectedCnt != 0 { - timeoutTimer := time.NewTimer(waitTimeout * time.Second) + timeoutTimer := time.NewTimer(waitTimeout) select { case <-timeoutTimer.C: fmt.Println("Timed out!") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) done = true break case m := <-recvCh: @@ -242,12 +254,33 @@ var rootCmd = &cobra.Command{ receiveCnt++ if receiveCnt == expectedCnt { done = true + break } + } + } + + //wait an extra 5 seconds to make sure no messages were missed + done = false + timer := time.NewTimer(5*time.Second) + for !done { + select { + case <-timer.C: + done = true break + case m := <-recvCh: + fmt.Printf("Message received: %s\n", string( + m.Payload)) + //fmt.Printf("%s", m.Timestamp) + receiveCnt++ } } - fmt.Printf("Received %d\n", receiveCnt) + jww.INFO.Printf("Received %d/%d Messages!", receiveCnt, expectedCnt) + fmt.Printf("Received %d\n", receiveCnt) + if roundsNotepad != nil { + roundsNotepad.INFO.Printf("\n%s", client.GetNetworkInterface().GetVerboseRounds()) + } + wg.Wait() err = client.StopNetworkFollower() if err != nil { jww.WARN.Printf( @@ -340,7 +373,8 @@ func printRoundResults(allRoundsSucceeded, timedOut bool, } func createClient() *api.Client { - initLog(viper.GetUint("logLevel"), viper.GetString("log")) + logLevel := viper.GetUint("logLevel") + initLog(logLevel, viper.GetString("log")) jww.INFO.Printf(Version()) pass := viper.GetString("password") @@ -383,6 +417,7 @@ func createClient() *api.Client { netParams.ForceHistoricalRounds = viper.GetBool("forceHistoricalRounds") netParams.FastPolling = !viper.GetBool("slowPolling") netParams.ForceMessagePickupRetry = viper.GetBool("forceMessagePickupRetry") + netParams.VerboseRoundTracking = viper.GetBool("verboseRoundTracking") client, err := api.OpenClient(storeDir, []byte(pass), netParams) if err != nil { @@ -410,6 +445,7 @@ func initClient() *api.Client { jww.INFO.Printf("Setting Uncheck Round Period to %v", period) netParams.UncheckRoundPeriod = period } + netParams.VerboseRoundTracking = viper.GetBool("verboseRoundTracking") //load the client client, err := api.Login(storeDir, []byte(pass), netParams) @@ -672,6 +708,10 @@ func initLog(threshold uint, logPath string) { jww.SetStdoutThreshold(jww.LevelInfo) jww.SetLogThreshold(jww.LevelInfo) } + + if viper.GetBool("verboseRoundTracking") { + initRoundLog(logPath) + } } func askToCreateChannel(recipientID *id.ID) bool { @@ -690,6 +730,23 @@ func askToCreateChannel(recipientID *id.ID) bool { } } +// this the the nodepad used for round logging. +var roundsNotepad *jww.Notepad + +// initRoundLog creates the log output for round tracking. In debug mode, +// the client will keep track of all rounds it evaluates if it has +// messages in, and then will dump them to this log on client exit +func initRoundLog(logPath string) { + parts := strings.Split(logPath, ".") + path := parts[0] + "-rounds." + parts[1] + logOutput, err := os.OpenFile(path, + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + roundsNotepad = jww.NewNotepad(jww.LevelInfo, jww.LevelInfo, ioutil.Discard, logOutput, "", log.Ldate|log.Ltime) +} + // init is the initialization function for Cobra which defines commands // and flags. func init() { @@ -706,6 +763,11 @@ func init() { "Verbose mode for debugging") viper.BindPFlag("logLevel", rootCmd.PersistentFlags().Lookup("logLevel")) + rootCmd.PersistentFlags().Bool("verboseRoundTracking", false, + "Verbose round tracking, keeps track and prints all rounds the "+ + "client was aware of while running. Defaults to false if not set.") + viper.BindPFlag("verboseRoundTracking", rootCmd.PersistentFlags().Lookup("verboseRoundTracking")) + rootCmd.PersistentFlags().StringP("session", "s", "", "Sets the initial storage directory for "+ "client session data") diff --git a/cmd/single.go b/cmd/single.go index 85124107e7ddf46a07eef113ce158b2a15b5303e..6600cbfbbf9b9358d79163c555096127609379d2 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -88,6 +88,17 @@ var singleCmd = &cobra.Command{ if err != nil { jww.FATAL.Panicf("Could not add single use process: %+v", err) } + + for numReg, total := 1, 100; numReg < total; { + time.Sleep(1 * time.Second) + numReg, total, err = client.GetNodeRegistrationStatus() + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + jww.INFO.Printf("Registering with nodes (%d/%d)...", + numReg, total) + } + timeout := viper.GetDuration("timeout") // If the send flag is set, then send a message diff --git a/cmd/version.go b/cmd/version.go index f01e1f873e867f17b27ef7913ad1ead7e1d69315..51a4bb42caeb6f5ac9da3a38fb47eceabc6faf7f 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.8.0" +const currentVersion = "2.10.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 ba23f84966c59216c9a53dc940e7a2a56da16e65..dbe4d79a58accf98ee34cc3b003b82eb7dcac69e 100644 --- a/go.mod +++ b/go.mod @@ -17,16 +17,17 @@ require ( 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.20210914232530-b0e625b49552 - gitlab.com/elixxir/crypto v0.0.7-0.20210914232212-42464d16fff3 + gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 + gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e gitlab.com/elixxir/ekv v0.1.5 - gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e - gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c - gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4 - gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 + gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae + gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 + gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect + google.golang.org/grpc v1.38.0 google.golang.org/protobuf v1.26.0 gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 14da1adea1978dd4659c4b3ed18e93bc6c1f4ea7..37d23fd0792d608122530d1b2d168f77b572f356 100644 --- a/go.sum +++ b/go.sum @@ -253,32 +253,34 @@ 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.20210914232530-b0e625b49552 h1:RgyEauSNIAlGo8Ynec01N3GKWWNNmemPLaMW899sCw0= -gitlab.com/elixxir/comms v0.0.4-0.20210914232530-b0e625b49552/go.mod h1:aXUf9T/1ddQYZ1+/fyhqvqHHxzu2QWB0XdODygWqzi8= +gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 h1:mSf5KidH231esbVvL1rozvLXhgAHn8S+BV70k0oxlW8= +gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970/go.mod h1:L2fs1Me+L6SKyix7+Gyd9QKmBMjnyJPds/ikSPqdeNY= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20210914232212-42464d16fff3 h1:IK6a8xloclNFxqYTzyCuiqP4cynmtxjzUuB8aNJtHy4= -gitlab.com/elixxir/crypto v0.0.7-0.20210914232212-42464d16fff3/go.mod h1:3ZGqugc0m5Y236n6mDyfsEy6j8C1HRlqTRj9I7t8k/w= +gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d h1:tI3YYoHVb/KViRhagzQM0XKdw0hJ7KcuSQXFIWhmtSE= +gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= +gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e h1:JCYcXV9GBvOVRfYhm1e2b52AnvPQrEQLmOR9XbjzE8k= +gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= 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/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e h1:h7i+Ld2pTlQ0oT2/KLsF6pVBI8D5ir5ZAP4RQBd/CzM= -gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e/go.mod h1:vTeq2GfYvYydmjhiVC188XeA7GBuUzJ1EZYc1hRwLZs= +gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 h1:sXX3/hewV4TQLxT2iKBfnfgW/A1eXoEfv5raJxTb79s= +gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576/go.mod h1:zZy8AlOISFm5IG4G4sylypnz7xNBfZ5mpXiibqJT8+8= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c h1:qoSX9V9rdSIuXXUQgZfY7Hkim9bYsg8XORVaOI8XD5U= -gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c/go.mod h1:Api5Gu+sx1I43THNGKtZOXItJpoGgCLT8KoP7vnLLSc= +gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae h1:jmZWmSm8eH40SX5B5uOw2XaYoHYqVn8daTfa6B80AOs= +gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae/go.mod h1:wR9Vx0KZLrIs0g2Efcp0UwFPStjcDRWkg/DJLVQI2vw= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4 h1:33a7mKkqsZUxzmG35hA1CaVZzyPZYe23if1GxQ99tpI= -gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4/go.mod h1:g0Lr/aM0KZqneIvSsKEn7zfNmorhq0R7IQn8Yh8fqbs= +gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 h1:mEjKISxi9LrguYgz6evroFwsfxH78/hYmr32yws+WV0= +gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686/go.mod h1:GeUUB5eMlu7G1u7LXpClfOyUYsSDxAhiZBf+RZeGftc= 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/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= -gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df h1:ukBhRj5o0gkbYos9+7SblXaxnMaty3ugQhWqb2BDcSE= -gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= +gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb h1:0K9dyxFpDYzH9jYLwzg3+bRj9a0uJjwjQkMeIdTxduQ= +gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 h1:FY+4Rh1Q2rgLyv10aKJjhWApuKRCR/054XhreudfAvw= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -299,8 +301,8 @@ golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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= @@ -367,8 +369,9 @@ golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= diff --git a/groupChat/groupStore/dhKeyList_test.go b/groupChat/groupStore/dhKeyList_test.go index df8d9f1dd4923970d1b97375b9e1bf48591378be..c8a633d441cf57c8775d384d817e985e3de59ea5 100644 --- a/groupChat/groupStore/dhKeyList_test.go +++ b/groupChat/groupStore/dhKeyList_test.go @@ -74,7 +74,6 @@ func TestDeserializeDhKeyList_DhKeyBinaryDecodeError(t *testing.T) { func TestDhKeyList_GoString(t *testing.T) { grp := createTestGroup(rand.New(rand.NewSource(42)), t) expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}" - if grp.DhKeys.GoString() != expected { t.Errorf("GoString failed to return the expected string."+ "\nexpected: %s\nreceived: %s", expected, grp.DhKeys.GoString()) diff --git a/groupChat/makeGroup.go b/groupChat/makeGroup.go index fa230fadcc99f61a4b4a44d0f62aa549ec9ec306..2c277b4384a04e187e722488d43b99357f1eb0e7 100644 --- a/groupChat/makeGroup.go +++ b/groupChat/makeGroup.go @@ -9,6 +9,7 @@ package groupChat import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/fastRNG" @@ -79,6 +80,9 @@ func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group, return gs.Group{}, nil, NotSent, errors.Errorf(addGroupErr, err) } + jww.DEBUG.Printf("Created new group %q with ID %s and members %s", + g.Name, g.ID, g.Members) + // Send all group requests roundIDs, status, err := m.sendRequests(g) diff --git a/groupChat/manager.go b/groupChat/manager.go index 2e2d18fadee2e5e246cf3ea42951305e14053963..6691e54154e613678d3b50684aa333a63b9c1f64 100644 --- a/groupChat/manager.go +++ b/groupChat/manager.go @@ -9,6 +9,7 @@ package groupChat import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces" @@ -127,6 +128,8 @@ func (m Manager) JoinGroup(g gs.Group) error { return errors.Errorf(joinGroupErr, g.ID, err) } + jww.DEBUG.Printf("Joined group %s.", g.ID) + return nil } @@ -136,17 +139,21 @@ func (m Manager) LeaveGroup(groupID *id.ID) error { return errors.Errorf(leaveGroupErr, groupID, err) } + jww.DEBUG.Printf("Left group %s.", groupID) + return nil } // GetGroups returns a list of all registered groupChat IDs. func (m Manager) GetGroups() []*id.ID { + jww.DEBUG.Print("Getting list of all groups.") return m.gs.GroupIDs() } // GetGroup returns the group with the matching ID or returns false if none // exist. func (m Manager) GetGroup(groupID *id.ID) (gs.Group, bool) { + jww.DEBUG.Printf("Getting group with ID %s.", groupID) return m.gs.Get(groupID) } diff --git a/groupChat/send.go b/groupChat/send.go index f2baa0953555b13f335111af4d1dd7ae0e01731e..cdb40888ee41c3489fe23f026c1f7e5d05f3caf6 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -9,6 +9,7 @@ package groupChat import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/crypto/group" @@ -48,6 +49,8 @@ func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, error) { return 0, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) } + jww.DEBUG.Printf("Sent message to group %s.", groupID) + return rid, nil } diff --git a/groupChat/sendRequests.go b/groupChat/sendRequests.go index 70c5501ec7c6492a1b33f5a5309b2602c38ad06e..cd7913fcf9488d1a5927d4171f7677e99cf245c6 100644 --- a/groupChat/sendRequests.go +++ b/groupChat/sendRequests.go @@ -10,6 +10,7 @@ package groupChat import ( "github.com/golang/protobuf/proto" "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" @@ -34,6 +35,8 @@ func (m Manager) ResendRequest(groupID *id.ID) ([]id.Round, RequestStatus, error return nil, NotSent, errors.Errorf(resendGroupIdErr, groupID) } + jww.DEBUG.Printf("Resending group requests for group %s.", groupID) + return m.sendRequests(g) } diff --git a/groupChat/utils_test.go b/groupChat/utils_test.go index df7fc4e35b68bad6d9a57fb7e2c34ef6f15613c0..d43ada0dc827fcff5fe253d9d89189abf588f778 100644 --- a/groupChat/utils_test.go +++ b/groupChat/utils_test.go @@ -261,6 +261,10 @@ func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Rou return []id.Round{}, nil } +func (tnm *testNetworkManager) GetVerboseRounds() string { + return "" +} + func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) { return 0, ephemeral.Id{}, nil } diff --git a/interfaces/networkManager.go b/interfaces/networkManager.go index 072306fddc18f53c7f07299dcffb52e64cdce853..b0d5351b70e290ec024d5d4d36e72c2eed2f4f1b 100644 --- a/interfaces/networkManager.go +++ b/interfaces/networkManager.go @@ -38,6 +38,9 @@ type NetworkManager interface { // address size is known. GetAddressSize() uint8 + // GetVerboseRounds returns stringification of verbose round info + GetVerboseRounds() string + // RegisterAddressSizeNotification returns a channel that will trigger for // every address space size update. The provided tag is the unique ID for // the channel. Returns an error if the tag is already used. diff --git a/interfaces/params/network.go b/interfaces/params/network.go index d3ee2fd39f4dd528bcb62a972c135c9725eb9768..42d0e7ec45141cc105ab5e33a1b3ba2b77d6ae1e 100644 --- a/interfaces/params/network.go +++ b/interfaces/params/network.go @@ -31,6 +31,9 @@ type Network struct { FastPolling bool // Messages will not be sent to Rounds containing these Nodes BlacklistedNodes []string + // Determines if the state of every round processed is tracked in ram. + // This is very memory intensive and is primarily used for debugging + VerboseRoundTracking bool Rounds Messages @@ -46,10 +49,11 @@ func GetDefaultNetwork() Network { RegNodesBufferLen: 500, NetworkHealthTimeout: 30 * time.Second, E2EParams: GetDefaultE2ESessionParams(), - ParallelNodeRegistrations: 8, + ParallelNodeRegistrations: 20, KnownRoundsThreshold: 1500, //5 rounds/sec * 60 sec/min * 5 min FastPolling: true, BlacklistedNodes: make([]string, 0), + VerboseRoundTracking: false, } n.Rounds = GetDefaultRounds() n.Messages = GetDefaultMessage() diff --git a/interfaces/utility/trackResults.go b/interfaces/utility/trackResults.go index cd766624d475141edbad94731f3a3da092e4bcfd..a02f1e0e4d2442b7a9979dd50dd93c2976d02fe6 100644 --- a/interfaces/utility/trackResults.go +++ b/interfaces/utility/trackResults.go @@ -8,6 +8,7 @@ package utility import ( + jww "github.com/spf13/jwalterweatherman" ds "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/primitives/states" ) @@ -22,6 +23,7 @@ func TrackResults(resultsCh chan ds.EventReturn, numResults int) (bool, int, int if er.TimedOut { numTimeOut++ } else if states.Round(er.RoundInfo.State) == states.FAILED { + jww.ERROR.Printf("RoundInfo FAILED: %+v", er.RoundInfo) numRoundFail++ } } diff --git a/keyExchange/trigger.go b/keyExchange/trigger.go index e25da821f9a3485969608bd320a7f22534622255..0bdcbcc6e6ca80a28b58f53bbf32f22a2b0d0d41 100644 --- a/keyExchange/trigger.go +++ b/keyExchange/trigger.go @@ -150,13 +150,13 @@ func handleTrigger(sess *storage.Session, net interfaces.NetworkManager, "transmit %v/%v paritions: %v round failures, %v timeouts", session, numRoundFail+numTimeOut, len(rounds), numRoundFail, numTimeOut) - sess.GetCriticalMessages().Failed(m) + sess.GetCriticalMessages().Failed(m, e2eParams) return nil } // otherwise, the transmission is a success and this should be denoted // in the session and the log - sess.GetCriticalMessages().Succeeded(m) + sess.GetCriticalMessages().Succeeded(m, e2eParams) jww.INFO.Printf("Key Negotiation transmission for %s successfully", session) diff --git a/keyExchange/utils_test.go b/keyExchange/utils_test.go index 0a1b0206bc8e72bf3ca25b4dd17e4ce02a5937d1..6acb4a73a6224a0230a32ec3a4b3ccabe3738814 100644 --- a/keyExchange/utils_test.go +++ b/keyExchange/utils_test.go @@ -79,6 +79,8 @@ func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe) return nil, nil } +func (t *testNetworkManagerGeneric) GetVerboseRounds() string { return "" } + func (t *testNetworkManagerGeneric) SendCMIX(message format.Message, rid *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) { return id.Round(0), ephemeral.Id{}, nil @@ -177,6 +179,8 @@ func (t *testNetworkManagerFullExchange) CheckGarbledMessages() { return } +func (t *testNetworkManagerFullExchange) GetVerboseRounds() string { return "" } + // Intended for alice to send to bob. Trigger's Bob's confirmation, chaining the operation // together func (t *testNetworkManagerFullExchange) SendE2E(message.Send, params.E2E, *stoppable.Single) ( diff --git a/network/ephemeral/testutil.go b/network/ephemeral/testutil.go index 1051e9c297d84a22436a08429f8f023deb953698..634518a1a6ba255fc55b47b092e1ed6b42efe1a7 100644 --- a/network/ephemeral/testutil.go +++ b/network/ephemeral/testutil.go @@ -96,7 +96,8 @@ func (t *testNetworkManager) GetSender() *gateway.Sender { return nil } -func (t *testNetworkManager) GetAddressSize() uint8 { return 15 } +func (t *testNetworkManager) GetAddressSize() uint8 { return 15 } +func (t *testNetworkManager) GetVerboseRounds() string { return "" } func (t *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { return nil, nil } diff --git a/network/follow.go b/network/follow.go index 2082a6ee4b5417a7de66b4ebc76209deff9072b7..ca0634be7f36a44e00c487046e488ee57525a55b 100644 --- a/network/follow.go +++ b/network/follow.go @@ -56,6 +56,13 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport, TrackTicker := time.NewTicker(debugTrackPeriod) rng := m.Rng.GetStream() + abandon := func(round id.Round) { return } + if m.verboseRounds != nil { + abandon = func(round id.Round) { + m.verboseRounds.denote(round, Abandoned) + } + } + for { select { case <-stop.Quit(): @@ -63,7 +70,7 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport, stop.ToStopped() return case <-ticker.C: - m.follow(report, rng, m.Comms, stop) + m.follow(report, rng, m.Comms, stop, abandon) case <-TrackTicker.C: numPolls := atomic.SwapUint64(m.tracker, 0) if m.numLatencies != 0 { @@ -94,7 +101,7 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport, // executes each iteration of the follower func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, - comms followNetworkComms, stop *stoppable.Single) { + comms followNetworkComms, stop *stoppable.Single, abandon func(round id.Round)) { //Get the identity we will poll for identity, err := m.Session.Reception().GetIdentity(rng, m.addrSpace.GetWithoutWait()) @@ -262,7 +269,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, } if len(pollResp.Filters.Filters) == 0 { - jww.TRACE.Printf("No filters found for the passed ID %d (%s), "+ + jww.WARN.Printf("No filters found for the passed ID %d (%s), "+ "skipping processing.", identity.EphId.Int64(), identity.Source) return } @@ -272,6 +279,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, //check if there are any valid filters returned if outOfBounds { + jww.WARN.Printf("No filters processed, none in valid range") return } @@ -287,13 +295,25 @@ 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, identity.CR) + hasMessage := rounds.Checker(rid, filterList, identity.CR) + if !hasMessage && m.verboseRounds != nil { + m.verboseRounds.denote(rid, RoundState(NoMessageAvailable)) + } + return hasMessage } // move the earliest unknown round tracker forward to the earliest // tracked round if it is behind earliestTrackedRound := id.Round(pollResp.EarliestRound) - updated, _ := identity.ER.Set(earliestTrackedRound) + updated, old, _ := identity.ER.Set(earliestTrackedRound) + if old == 0 { + if gwRoundsState.GetLastChecked() > id.Round(m.param.KnownRoundsThreshold) { + updated = gwRoundsState.GetLastChecked() - id.Round(m.param.KnownRoundsThreshold) + } else { + updated = 1 + } + identity.ER.Set(updated) + } // loop through all rounds the client does not know about and the gateway // does, checking the bloom filter for the user to see if there are @@ -301,7 +321,8 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, //threshold is the earliest round that will not be excluded from earliest remaining earliestRemaining, roundsWithMessages, roundsUnknown := gwRoundsState.RangeUnchecked(updated, m.param.KnownRoundsThreshold, roundChecker) - _, changed := identity.ER.Set(earliestRemaining) + + _, _, changed := identity.ER.Set(earliestRemaining) if changed { jww.TRACE.Printf("External returns of RangeUnchecked: %d, %v, %v", earliestRemaining, roundsWithMessages, roundsUnknown) jww.DEBUG.Printf("New Earliest Remaining: %d", earliestRemaining) @@ -312,9 +333,10 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, return rounds.Checker(rid, filterList, identity.CR) } return false - }, roundsUnknown) + }, roundsUnknown, abandon) for _, rid := range roundsWithMessages { + //denote that the round has been looked at in the tracking store if identity.CR.Check(rid) { m.round.GetMessagesFromRound(rid, identity) } @@ -330,4 +352,32 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, for _, rid := range roundsWithMessages2 { m.round.GetMessagesFromRound(rid, identity) } + + if m.verboseRounds != nil { + trackingStart := updated + if uint(earliestRemaining-updated) > m.param.KnownRoundsThreshold { + trackingStart = earliestRemaining - id.Round(m.param.KnownRoundsThreshold) + } + jww.DEBUG.Printf("Rounds tracked: %v to %v", trackingStart, earliestRemaining) + for i := trackingStart; i <= earliestRemaining; i++ { + state := Unchecked + for _, rid := range roundsWithMessages { + if rid == i { + state = MessageAvailable + } + } + for _, rid := range roundsWithMessages2 { + if rid == i { + state = MessageAvailable + } + } + for _, rid := range roundsUnknown { + if rid == i { + state = Unknown + } + } + m.verboseRounds.denote(i, RoundState(state)) + } + } + } diff --git a/network/gateway/hostPool.go b/network/gateway/hostPool.go index 52139d1a185b760c9d23cf408e65a86c7d2c67bd..5bb0b82044058f8c51f055482cdd00a1b6bf37d9 100644 --- a/network/gateway/hostPool.go +++ b/network/gateway/hostPool.go @@ -18,12 +18,16 @@ import ( "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/elixxir/crypto/shuffle" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/balancer" "io" "math" + "sort" "strings" "sync" "time" @@ -31,8 +35,8 @@ import ( // List of errors that initiate a Host replacement var errorsList = []string{context.DeadlineExceeded.Error(), "connection refused", "host disconnected", - "transport is closing", "all SubConns are in TransientFailure", "Last try to connect", - ndf.NO_NDF, "Host is in cool down"} + "transport is closing", balancer.ErrTransientFailure.Error(), "Last try to connect", + ndf.NO_NDF, "Host is in cool down", grpc.ErrClientConnClosing.Error()} // HostManager Interface allowing storage and retrieval of Host objects type HostManager interface { @@ -70,10 +74,10 @@ type HostPool struct { // 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 + MaxPoolSize uint32 // Maximum number of Hosts in the HostPool + PoolSize uint32 // Allows override of HostPool size. Set to zero for dynamic size calculation ProxyAttempts uint32 // How many proxies will be used in event of send failure + MaxPings uint32 // How many gateways to concurrently test when initializing HostPool. Disabled if zero. HostParams connect.HostParams // Parameters for the creation of new Host objects } @@ -83,6 +87,7 @@ func DefaultPoolParams() PoolParams { MaxPoolSize: 30, ProxyAttempts: 5, PoolSize: 0, + MaxPings: 0, HostParams: connect.GetDefaultHostParams(), } p.HostParams.MaxRetries = 1 @@ -91,6 +96,7 @@ func DefaultPoolParams() PoolParams { p.HostParams.NumSendsBeforeCoolOff = 1 p.HostParams.CoolOffTimeout = 5 * time.Minute p.HostParams.SendTimeout = 2000 * time.Millisecond + p.HostParams.PingTimeout = 1 * time.Second return p } @@ -114,7 +120,7 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, hostMap: make(map[id.ID]uint32), hostList: make([]*connect.Host, poolParams.PoolSize), poolParams: poolParams, - ndf: netDef, + ndf: netDef.DeepCopy(), rng: rng, storage: storage, addGatewayChan: addGateway, @@ -141,6 +147,9 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, jww.WARN.Printf("Unable to add stored host %s: %s", hid, err.Error()) } else { numHostsAdded++ + if numHostsAdded >= len(result.hostList) { + break + } } } } else { @@ -148,17 +157,151 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, } // Build the initial HostPool and return - for i := numHostsAdded; i < len(result.hostList); i++ { - err := result.forceReplace(uint32(i)) + if result.poolParams.MaxPings > 0 { + // If pinging enabled, select random performant Hosts + err = result.initialize(uint32(numHostsAdded)) if err != nil { return nil, err } + } else { + // Else, select random Hosts + for i := numHostsAdded; i < len(result.hostList); i++ { + err := result.replaceHost(result.selectGateway(), uint32(i)) + if err != nil { + return nil, err + } + } } jww.INFO.Printf("Initialized HostPool with size: %d/%d", poolParams.PoolSize, len(netDef.Gateways)) return result, nil } +// Initialize the HostPool with a performant set of Hosts +func (h *HostPool) initialize(startIdx uint32) error { + // If HostPool is full, don't need to initialize + if startIdx == h.poolParams.PoolSize { + return nil + } + + // Randomly shuffle gateways in NDF + randomGateways := make([]ndf.Gateway, len(h.ndf.Gateways)) + copy(randomGateways, h.ndf.Gateways) + var rndBytes [32]byte + stream := h.rng.GetStream() + _, err := stream.Read(rndBytes[:]) + stream.Close() + if err != nil { + return errors.Errorf("Failed to randomize shuffle for HostPool initialization: %+v", err) + } + shuffle.ShuffleSwap(rndBytes[:], len(randomGateways), func(i, j int) { + randomGateways[i], randomGateways[j] = randomGateways[j], randomGateways[i] + }) + + // Set constants + type gatewayDuration struct { + id *id.ID + latency time.Duration + } + numGatewaysToTry := h.poolParams.MaxPings + numGateways := uint32(len(randomGateways)) + if numGatewaysToTry > numGateways { + numGatewaysToTry = numGateways + } + resultList := make([]gatewayDuration, 0, numGatewaysToTry) + + // Begin trying gateways + c := make(chan gatewayDuration, numGatewaysToTry) + exit := false + i := uint32(0) + for !exit { + for ; i < numGateways; i++ { + // Ran out of Hosts to try + if i >= numGateways { + exit = true + break + } + + // Select a gateway not yet selected + gwId, err := randomGateways[i].GetGatewayId() + if err != nil { + jww.WARN.Printf("ID for gateway %s could not be retrieved", gwId) + } + // Skip if already in HostPool + if _, ok := h.hostMap[*gwId]; ok { + // Try another Host instead + numGatewaysToTry++ + continue + } + + go func() { + // Obtain that GwId's Host object + newHost, ok := h.manager.GetHost(gwId) + if !ok { + jww.WARN.Printf("Host for gateway %s could not be "+ + "retrieved", gwId) + return + } + + // Ping the Host latency and send the result + jww.DEBUG.Printf("Testing host %s...", gwId.String()) + latency, _ := newHost.IsOnline() + c <- gatewayDuration{gwId, latency} + }() + } + + // Collect ping results + timer := time.NewTimer(2 * h.poolParams.HostParams.PingTimeout) + innerLoop: + for { + select { + case gw := <-c: + // Only add successful pings + if gw.latency > 0 { + resultList = append(resultList, gw) + jww.DEBUG.Printf("Adding HostPool result %d/%d: %s: %d", + len(resultList), numGatewaysToTry, gw.id.String(), gw.latency) + } + + // Break if we have all needed slots + if uint32(len(resultList)) == numGatewaysToTry { + exit = true + break innerLoop + } + case <-timer.C: + jww.INFO.Printf("HostPool initialization timed out!") + break innerLoop + } + } + } + + // Sort the resultList by lowest latency + sort.Slice(resultList, func(i, j int) bool { + return resultList[i].latency < resultList[j].latency + }) + jww.DEBUG.Printf("Gateway pool results: %+v", resultList) + + // Process ping results + for _, result := range resultList { + err = h.replaceHost(result.id, startIdx) + if err != nil { + jww.WARN.Printf("Unable to replaceHost: %+v", err) + continue + } + startIdx++ + if startIdx >= h.poolParams.PoolSize { + break + } + } + + // Ran out of Hosts to try + if startIdx < h.poolParams.PoolSize { + return errors.Errorf("Unable to initialize enough viable hosts for HostPool") + } + + return nil +} + // UpdateNdf Mutates internal ndf to the given ndf func (h *HostPool) UpdateNdf(ndf *ndf.NetworkDefinition) { if len(ndf.Gateways) == 0 { @@ -167,7 +310,7 @@ func (h *HostPool) UpdateNdf(ndf *ndf.NetworkDefinition) { } h.ndfMux.Lock() - h.ndf = ndf + h.ndf = ndf.DeepCopy() h.hostMux.Lock() err := h.updateConns() @@ -186,6 +329,26 @@ func (h *HostPool) SetFilter(f Filter) { h.filter = f } +// GetHostParams returns a copy of the host parameters struct +func (h *HostPool) GetHostParams() connect.HostParams { + hp := h.poolParams.HostParams + hpCopy := connect.HostParams{ + MaxRetries: hp.MaxRetries, + AuthEnabled: hp.AuthEnabled, + EnableCoolOff: hp.EnableCoolOff, + NumSendsBeforeCoolOff: hp.NumSendsBeforeCoolOff, + CoolOffTimeout: hp.CoolOffTimeout, + SendTimeout: hp.SendTimeout, + EnableMetrics: hp.EnableMetrics, + ExcludeMetricErrors: make([]string, len(hp.ExcludeMetricErrors)), + KaClientOpts: hp.KaClientOpts, + } + for i := 0; i < len(hp.ExcludeMetricErrors); i++ { + hpCopy.ExcludeMetricErrors[i] = hp.ExcludeMetricErrors[i] + } + return hpCopy +} + // getFilter returns the filter used to filter gateways from the ID map. func (h *HostPool) getFilter() Filter { h.filterMux.Lock() @@ -281,7 +444,7 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) { if hostErr != nil { for _, errString := range errorsList { if strings.Contains(hostErr.Error(), errString) { - // Host needs replaced, flag and continue + // Host needs to be replaced, flag and continue doReplace = true break } @@ -294,7 +457,7 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) { if oldPoolIndex, ok := h.hostMap[*hostId]; ok { // Replace it h.ndfMux.RLock() - err = h.forceReplace(oldPoolIndex) + err = h.replaceHost(h.selectGateway(), oldPoolIndex) h.ndfMux.RUnlock() } h.hostMux.Unlock() @@ -302,8 +465,8 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) { return doReplace, err } -// Replace given Host index with a new, randomly-selected Host from the NDF -func (h *HostPool) forceReplace(oldPoolIndex uint32) error { +// Select a viable HostPool candidate from the NDF +func (h *HostPool) selectGateway() *id.ID { rng := h.rng.GetStream() defer rng.Close() @@ -311,18 +474,27 @@ func (h *HostPool) forceReplace(oldPoolIndex uint32) error { 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") + jww.WARN.Printf("Unable to unmarshal gateway: %+v", err) + continue + } + + // Verify the Gateway's Node is not Stale before adding to HostPool + nodeId := gwId.DeepCopy() + nodeId.SetType(id.Node) + nodeNdfIdx := h.ndfMap[*nodeId] + isNodeStale := h.ndf.Nodes[nodeNdfIdx].Status == ndf.Stale + if isNodeStale { + jww.DEBUG.Printf("Ignoring stale node: %s", nodeId.String()) + continue } // 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) + return gwId } } } @@ -335,7 +507,7 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { return err } - // Convert list of of non-nil and non-zero hosts to ID list + // Convert list of non-nil and non-zero hosts to ID list idList := make([]*id.ID, 0, len(h.hostList)) for _, host := range h.hostList { if host.GetId() != nil && !host.GetId().Cmp(&id.ID{}) { @@ -348,7 +520,7 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { } // replaceHostNoStore replaces the given slot in the HostPool with a new Gateway -// with the specified ID. +// with the specified ID without saving changes to storage func (h *HostPool) replaceHostNoStore(newId *id.ID, oldPoolIndex uint32) error { // Obtain that GwId's Host object newHost, ok := h.manager.GetHost(newId) @@ -367,11 +539,14 @@ func (h *HostPool) replaceHostNoStore(newId *id.ID, oldPoolIndex uint32) error { h.hostMap[*newId] = oldPoolIndex // Clean up and move onto next Host + oldHostIDStr := "unknown" if oldHost != nil { + oldHostIDStr = oldHost.GetId().String() delete(h.hostMap, *oldHost.GetId()) go oldHost.Disconnect() } - jww.DEBUG.Printf("Replaced Host at %d with new Host %s", oldPoolIndex, + + jww.DEBUG.Printf("Replaced Host at %d [%s] with new Host %s", oldPoolIndex, oldHostIDStr, newId.String()) return nil @@ -406,24 +581,27 @@ func (h *HostPool) updateConns() error { // Filter out gateway IDs newMap = h.getFilter()(newMap, h.ndf) + // Keep track of the old NDF set + oldMap := h.ndfMap + // Update the internal NDF set + h.ndfMap = newMap + // Handle adding Gateways for gwId, ndfIdx := range newMap { - if _, ok := h.ndfMap[gwId]; !ok { + if _, ok := oldMap[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 { + for gwId := range oldMap { 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 } @@ -434,7 +612,8 @@ func convertNdfToMap(ndf *ndf.NetworkDefinition) (map[id.ID]int, error) { return result, nil } - // Process gateway Id's into set + // Process Node and Gateway Ids into set + // NOTE: We expect len(ndf.Gateways) == len(ndf.Nodes) for i := range ndf.Gateways { gw := ndf.Gateways[i] gwId, err := id.Unmarshal(gw.ID) @@ -442,6 +621,13 @@ func convertNdfToMap(ndf *ndf.NetworkDefinition) (map[id.ID]int, error) { return nil, err } result[*gwId] = i + + node := ndf.Nodes[i] + nodeId, err := id.Unmarshal(node.ID) + if err != nil { + return nil, err + } + result[*nodeId] = i } return result, nil @@ -452,7 +638,7 @@ 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) + err := h.replaceHost(h.selectGateway(), poolIndex) if err != nil { jww.ERROR.Printf("Unable to removeGateway: %+v", err) } diff --git a/network/gateway/hostpool_test.go b/network/gateway/hostpool_test.go index e06d49dab9f88673e8a1fea3c4a364e7697c4190..919d3faedbada3445219de4886fc84ba2362cdeb 100644 --- a/network/gateway/hostpool_test.go +++ b/network/gateway/hostpool_test.go @@ -143,7 +143,7 @@ func TestHostPool_ManageHostPool(t *testing.T) { // Construct nodes nodeId := gwId.DeepCopy() nodeId.SetType(id.Node) - newNodes[i] = ndf.Node{ID: nodeId.Bytes()} + newNodes[i] = ndf.Node{ID: nodeId.Bytes(), Status: ndf.Active} } @@ -319,6 +319,17 @@ func TestHostPool_ForceReplace(t *testing.T) { params := DefaultPoolParams() params.PoolSize = uint32(len(testNdf.Gateways)) + // Add a stale node + newGateway := ndf.Gateway{ + ID: id.NewIdFromUInt(27, id.Gateway, t).Bytes(), + } + newNode := ndf.Node{ + ID: id.NewIdFromUInt(27, id.Node, t).Bytes(), + Status: ndf.Stale, + } + testNdf.Gateways = append(testNdf.Gateways, newGateway) + testNdf.Nodes = append(testNdf.Nodes, newNode) + // Pull all gateways from ndf into host manager for _, gw := range testNdf.Gateways { @@ -343,13 +354,14 @@ func TestHostPool_ForceReplace(t *testing.T) { } // Add all gateways to hostPool's map - for index, gw := range testNdf.Gateways { + for i := uint32(0); i < params.PoolSize; i++ { + gw := testNdf.Gateways[i] 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)) + err = testPool.replaceHost(gwId, i) if err != nil { t.Fatalf("Failed to replace host in set-up: %v", err) } @@ -359,7 +371,7 @@ func TestHostPool_ForceReplace(t *testing.T) { oldHost := testPool.hostList[oldGatewayIndex] // Force replace the gateway at a given index - err = testPool.forceReplace(uint32(oldGatewayIndex)) + err = testPool.replaceHost(testPool.selectGateway(), uint32(oldGatewayIndex)) if err != nil { t.Errorf("Failed to force replace: %v", err) } @@ -490,7 +502,7 @@ func TestHostPool_UpdateNdf(t *testing.T) { hostPool.UpdateNdf(newNdf) // Check that the host pool's ndf has been modified properly - if !reflect.DeepEqual(newNdf, hostPool.ndf) { + if len(newNdf.Nodes) != len(hostPool.ndf.Nodes) || len(newNdf.Gateways) != len(hostPool.ndf.Gateways) { t.Errorf("Host pool ndf not updated to new ndf.") } } @@ -792,7 +804,7 @@ func TestHostPool_UpdateConns_RemoveGateways(t *testing.T) { // Construct nodes nodeId := gwId.DeepCopy() nodeId.SetType(id.Node) - newNodes[i] = ndf.Node{ID: nodeId.Bytes()} + newNodes[i] = ndf.Node{ID: nodeId.Bytes(), Status: ndf.Active} } diff --git a/network/gateway/sender.go b/network/gateway/sender.go index ddd41478cc7845347bd5adde640794766cf9cc8f..9d2f3d3ad9fa96f3dee1ea987ef4e5b9f55635dd 100644 --- a/network/gateway/sender.go +++ b/network/gateway/sender.go @@ -18,6 +18,7 @@ import ( "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" + "strings" ) // Sender Object used for sending that wraps the HostPool for providing destinations @@ -25,6 +26,8 @@ type Sender struct { *HostPool } +const RetryableError = "Nonfatal error occurred, please retry" + // 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) { @@ -40,18 +43,32 @@ func NewSender(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.Net func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) { proxies := s.getAny(s.poolParams.ProxyAttempts, nil) - for i := range proxies { - result, err := sendFunc(proxies[i]) + for proxy := range proxies { + result, err := sendFunc(proxies[proxy]) if stop != nil && !stop.IsRunning() { return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToAny") } else 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) + } else if strings.Contains(err.Error(), RetryableError) { + // Retry of the proxy could not communicate + jww.INFO.Printf("Unable to SendToAny via %s: non-fatal error received, retrying: %s", + proxies[proxy].GetId().String(), err) + } else if strings.Contains(err.Error(), "unable to connect to target host") { + // Retry of the proxy could not communicate + jww.WARN.Printf("Unable to SendToAny via %s: %s,"+ + " proxy could not contact requested host", + proxies[proxy].GetId(), err) + continue + } else if replaced, checkReplaceErr := s.checkReplace(proxies[proxy].GetId(), err); replaced { + if checkReplaceErr != nil { + jww.WARN.Printf("Unable to SendToAny, replaced a proxy %s with error %s", + proxies[proxy].GetId().String(), checkReplaceErr) + } else { + jww.WARN.Printf("Unable to SendToAny, replaced a proxy %s", + proxies[proxy].GetId().String()) } + } else { + return nil, errors.WithMessage(err, "Received error with SendToAny") } } @@ -60,7 +77,7 @@ func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error // 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{}, bool, error), + sendFunc func(host *connect.Host, target *id.ID) (interface{}, error), stop *stoppable.Single) (interface{}, error) { // Get the hosts and shuffle randomly @@ -68,22 +85,38 @@ func (s *Sender) SendToPreferred(targets []*id.ID, // Attempt to send directly to targets if they are in the HostPool for i := range targetHosts { - result, didAbort, err := sendFunc(targetHosts[i], targets[i]) + result, err := sendFunc(targetHosts[i], targets[i]) if stop != nil && !stop.IsRunning() { return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToPreferred") } else if err == nil { return result, nil - } else { - if didAbort { - return nil, errors.WithMessagef(err, "Aborted SendToPreferred gateway %s", - targetHosts[i].GetId().String()) - } - jww.WARN.Printf("Unable to SendToPreferred %s via %s: %s", + } else if strings.Contains(err.Error(), RetryableError) { + // Retry of the proxy could not communicate + jww.INFO.Printf("Unable to to SendToPreferred first pass %s via %s: non-fatal error received, retrying: %s", + targets[i], targetHosts[i].GetId(), err) + } else if strings.Contains(err.Error(), "unable to connect to target host") { + // Retry of the proxy could not communicate + jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, "+ + "proxy could not contact requested host", targets[i], targetHosts[i].GetId(), err) - _, err = s.checkReplace(targetHosts[i].GetId(), err) - if err != nil { - jww.ERROR.Printf("Unable to checkReplace: %+v", err) + continue + } else if replaced, checkReplaceErr := s.checkReplace(targetHosts[i].GetId(), err); replaced { + if checkReplaceErr != nil { + jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s, "+ + "proxy failed, was replaced with error: %s", + targets[i], targetHosts[i].GetId(), checkReplaceErr) + } else { + jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s, "+ + "proxy failed, was replaced", + targets[i], targetHosts[i].GetId()) } + jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, proxy failed, was replaced", + targets[i], targetHosts[i].GetId(), checkReplaceErr) + continue + } else { + jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, comm returned an error", + targets[i], targetHosts[i].GetId(), err) + return result, err } } @@ -115,26 +148,37 @@ func (s *Sender) SendToPreferred(targets []*id.ID, continue } - result, didAbort, err := sendFunc(targetProxies[proxyIdx], target) + result, err := sendFunc(proxy, target) if stop != nil && !stop.IsRunning() { return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToPreferred") } else if err == nil { return result, nil - } else { - if didAbort { - return nil, errors.WithMessagef(err, "Aborted SendToPreferred gateway proxy %s", - proxy.GetId().String()) - } - jww.WARN.Printf("Unable to SendToPreferred %s via proxy "+ - "%s: %s", target, proxy.GetId(), err) - wasReplaced, err := s.checkReplace(proxy.GetId(), err) - if err != nil { - jww.ERROR.Printf("Unable to checkReplace: %+v", err) - } - // If the proxy was replaced, add as a bad proxy - if wasReplaced { - badProxies[proxy.String()] = nil + } else if strings.Contains(err.Error(), RetryableError) { + // Retry of the proxy could not communicate + jww.INFO.Printf("Unable to SendToPreferred second pass %s via %s: non-fatal error received, retrying: %s", + target, proxy, err) + } else if strings.Contains(err.Error(), "unable to connect to target host") { + // Retry of the proxy could not communicate + jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s: %s,"+ + " proxy could not contact requested host", + target, proxy, err) + continue + } else if replaced, checkReplaceErr := s.checkReplace(proxy.GetId(), err); replaced { + if checkReplaceErr != nil { + jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s,"+ + "proxy failed, was replaced with error: %s", target, proxy.GetId(), + checkReplaceErr) + } else { + jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s, "+ + "proxy failed, was replaced", target, proxy.GetId()) } + + badProxies[proxy.String()] = nil + continue + } else { + jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s: %s, comm returned an error", + target, proxy.GetId(), err) + return result, err } } } diff --git a/network/gateway/utils_test.go b/network/gateway/utils_test.go index 9f75ace1429e947ef7c8a399f3f088e2583777de..09081da17cc895becdd069ec7b794d53a11bc8e3 100644 --- a/network/gateway/utils_test.go +++ b/network/gateway/utils_test.go @@ -90,55 +90,67 @@ func getTestNdf(face interface{}) *ndf.NetworkDefinition { Nodes: []ndf.Node{{ ID: id.NewIdFromUInt(0, id.Node, face)[:], Address: "0.0.0.1", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(1, id.Node, face)[:], Address: "0.0.0.2", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(2, id.Node, face)[:], Address: "0.0.0.3", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(3, id.Node, face)[:], Address: "0.0.0.1", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(4, id.Node, face)[:], Address: "0.0.0.2", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(5, id.Node, face)[:], Address: "0.0.0.3", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(6, id.Node, face)[:], Address: "0.0.0.1", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(7, id.Node, face)[:], Address: "0.0.0.2", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(8, id.Node, face)[:], Address: "0.0.0.3", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(9, id.Node, face)[:], Address: "0.0.0.1", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(10, id.Node, face)[:], Address: "0.0.0.2", + Status: ndf.Active, }, { ID: id.NewIdFromUInt(11, id.Node, face)[:], Address: "0.0.0.3", + Status: ndf.Active, }}, } } const happyPathReturn = "happyPathReturn" -func SendToPreferred_HappyPath(host *connect.Host, target *id.ID) (interface{}, bool, error) { - return happyPathReturn, false, nil +func SendToPreferred_HappyPath(host *connect.Host, target *id.ID) (interface{}, error) { + return happyPathReturn, nil } -func SendToPreferred_KnownError(host *connect.Host, target *id.ID) (interface{}, bool, error) { - return nil, false, fmt.Errorf(errorsList[0]) +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{}, bool, error) { - return nil, false, fmt.Errorf("Unexpected error: Oopsie") +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) { diff --git a/network/health/tracker.go b/network/health/tracker.go index 7d5b6a7d9c36edd2723685276c385c114b20bc13..8be8c05ff98b7a37f9fad79e3b6572c846bf9f61 100644 --- a/network/health/tracker.go +++ b/network/health/tracker.go @@ -144,6 +144,7 @@ func (t *Tracker) setHealth(h bool) { func (t *Tracker) Start() (stoppable.Stoppable, error) { t.mux.Lock() if t.running { + t.mux.Unlock() return nil, errors.New("cannot start Health tracker threads, " + "they are already running") } @@ -162,8 +163,6 @@ func (t *Tracker) Start() (stoppable.Stoppable, error) { // start starts a long-running thread used to monitor and report on network // health. func (t *Tracker) start(stop *stoppable.Single) { - timer := time.NewTimer(t.timeout) - for { var heartbeat network.Heartbeat select { @@ -178,19 +177,13 @@ func (t *Tracker) start(stop *stoppable.Single) { return case heartbeat = <-t.heartbeat: + // FIXME: There's no transition to unhealthy here + // and there needs to be after some number of bad + // polls if healthy(heartbeat) { - // Stop and reset timer - if !timer.Stop() { - select { - // per docs explicitly drain - case <-timer.C: - default: - } - } - timer.Reset(t.timeout) t.setHealth(true) } - case <-timer.C: + case <-time.After(t.timeout): if !t.isHealthy { jww.WARN.Printf("Network health tracker timed out, network is no longer healthy...") } diff --git a/network/manager.go b/network/manager.go index e26c455b39c25b594780ef7695f84bf207414b9b..35f3c668247586618ea3d52e442b3800bc2efd76 100644 --- a/network/manager.go +++ b/network/manager.go @@ -34,7 +34,7 @@ import ( ) // Manager implements the NetworkManager interface inside context. It -// controls access to network resources and implements all of the communications +// controls access to network resources and implements all the communications // functions used by the client. type manager struct { // parameters of the network @@ -50,9 +50,10 @@ type manager struct { message *message.Manager //number of polls done in a period of time - tracker *uint64 - latencySum uint64 - numLatencies uint64 + tracker *uint64 + latencySum uint64 + numLatencies uint64 + verboseRounds *RoundTracker // Address space size addrSpace *ephemeral.AddressSpace @@ -89,6 +90,10 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, events: events, } + if params.VerboseRoundTracking { + m.verboseRounds = NewRoundTracker() + } + m.Internal = internal.Internal{ Session: session, Switchboard: switchboard, @@ -102,10 +107,15 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, Events: events, } + // Set up node registration chan for network instance + m.Instance.SetAddGatewayChan(m.NodeRegistration) + // Set up gateway.Sender poolParams := gateway.DefaultPoolParams() // Client will not send KeepAlive packets poolParams.HostParams.KaClientOpts.Time = time.Duration(math.MaxInt64) + // Enable optimized HostPool initialization + poolParams.MaxPings = 50 m.sender, err = gateway.NewSender(poolParams, rng, ndf, comms, session, m.NodeRegistration) if err != nil { @@ -223,3 +233,11 @@ func (m *manager) UnregisterAddressSizeNotification(tag string) { func (m *manager) SetPoolFilter(f gateway.Filter) { m.sender.SetFilter(f) } + +// GetVerboseRounds returns verbose round information +func (m *manager) GetVerboseRounds() string { + if m.verboseRounds == nil { + return "Verbose Round tracking not enabled" + } + return m.verboseRounds.String() +} diff --git a/network/message/critical.go b/network/message/critical.go index 9c5b6bff7c824a925c969459ddb683e38a1faa1d..1714e193b27f959c581c316ffb0c4c1efc496bab 100644 --- a/network/message/critical.go +++ b/network/message/critical.go @@ -51,8 +51,9 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) { //critical messages for msg, param, has := critMsgs.Next(); has; msg, param, has = critMsgs.Next() { go func(msg message.Send, param params.E2E) { - jww.INFO.Printf("Resending critical message to %s ", - msg.Recipient) + + jww.INFO.Printf("Resending critical message to %s with params %#v", + msg.Recipient, param) //send the message rounds, _, _, err := m.SendE2E(msg, param, stop) //if the message fail to send, notify the buffer so it can be handled @@ -61,7 +62,7 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) { jww.ERROR.Printf("Failed to send critical message to %s "+ " on notification of healthy network: %+v", msg.Recipient, err) - critMsgs.Failed(msg) + critMsgs.Failed(msg, param) return } //wait on the results to make sure the rounds were successful @@ -77,13 +78,13 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) { "to transmit transmit %v/%v paritions on rounds %d: %v "+ "round failures, %v timeouts", msg.Recipient, numRoundFail+numTimeOut, len(rounds), rounds, numRoundFail, numTimeOut) - critMsgs.Failed(msg) + critMsgs.Failed(msg, param) return } jww.INFO.Printf("Successful resend of critical message "+ "to %s on rounds %d", msg.Recipient, rounds) - critMsgs.Succeeded(msg) + critMsgs.Succeeded(msg, param) }(msg, param) } diff --git a/network/message/handler.go b/network/message/handler.go index 08ced621adaddc0b74772e82dfd02862ece95b5b..ed6891a755a885f186cb7352b60c588b61eb6d13 100644 --- a/network/message/handler.go +++ b/network/message/handler.go @@ -108,8 +108,8 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) { RoundId: id.Round(bundle.RoundInfo.ID), RoundTimestamp: time.Unix(0, int64(bundle.RoundInfo.Timestamps[states.QUEUED])), } - im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, "+ - "msgDigest: %s", msg.GetKeyFP(), msg.Digest()) + im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, round: %d"+ + "msgDigest: %s", msg.GetKeyFP(), bundle.Round, msg.Digest()) jww.INFO.Print(im) m.Internal.Events.Report(1, "MessageReception", "Garbled", im) m.Session.GetGarbledMessages().Add(msg) @@ -117,8 +117,8 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) { return } - im := fmt.Sprintf("Received message of type %s from %s,"+ - " msgDigest: %s", encTy, sender, msgDigest) + im := fmt.Sprintf("Received message of type %s from %s in round %d,"+ + " msgDigest: %s", encTy, sender, bundle.Round, msgDigest) jww.INFO.Print(im) m.Internal.Events.Report(2, "MessageReception", "MessagePart", im) diff --git a/network/message/sendCmix.go b/network/message/sendCmix.go index 2028e435692425f6df01d2bdcbc9d03087c37250..bfb57a56903cb9f4f23e46cae2c59657c0d06da1 100644 --- a/network/message/sendCmix.go +++ b/network/message/sendCmix.go @@ -21,11 +21,12 @@ import ( "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/primitives/format" + "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/netTime" - "strings" + "time" ) // WARNING: Potentially Unsafe @@ -40,6 +41,23 @@ func (m *Manager) SendCMIX(sender *gateway.Sender, msg format.Message, m.TransmissionID, m.Comms, stop) } +func calculateSendTimeout(best *pb.RoundInfo, max time.Duration) time.Duration { + RoundStartTime := time.Unix(0, + int64(best.Timestamps[states.QUEUED])) + // 250ms AFTER the round starts to hear the response. + timeout := RoundStartTime.Sub( + netTime.Now().Add(250 * time.Millisecond)) + if timeout > max { + timeout = max + } + // time.Duration is a signed int, so check for negative + if timeout < 0 { + // TODO: should this produce a warning? + timeout = 100 * time.Millisecond + } + return timeout +} + // Helper function for sendCmix // NOTE: Payloads send are not End to End encrypted, MetaData is NOT protected with // this call, see SendE2E for End to End encryption and full privacy protection @@ -58,6 +76,7 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message, timeStart := netTime.Now() attempted := set.New() + maxTimeout := sender.GetHostParams().SendTimeout jww.INFO.Printf("Looking for round to send cMix message to %s "+ "(msgDigest: %s)", recipient, msg.Digest()) @@ -84,6 +103,7 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message, jww.WARN.Printf("Failed to GetUpcomingRealtime (msgDigest: %s): %+v", msg.Digest(), err) } if bestRound == nil { + jww.WARN.Printf("Best round on send is nil") continue } @@ -128,19 +148,19 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message, encMsg.Digest(), firstGateway.String()) // Send the payload - sendFunc := func(host *connect.Host, target *id.ID) (interface{}, bool, error) { + sendFunc := func(host *connect.Host, target *id.ID) (interface{}, error) { wrappedMsg.Target = target.Marshal() - result, err := comms.SendPutMessage(host, wrappedMsg) + + timeout := calculateSendTimeout(bestRound, maxTimeout) + result, err := comms.SendPutMessage(host, wrappedMsg, + timeout) if err != nil { // fixme: should we provide as a slice the whole topology? - warn, err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipient.String(), bestRound, err) - if warn { - jww.WARN.Printf("SendCmix Failed: %+v", err) - } else { - return result, true, errors.WithMessagef(err, "SendCmix %s", unrecoverableError) - } + err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipient.String(), bestRound, err) + return result, errors.WithMessagef(err, "SendCmix %s", unrecoverableError) + } - return result, false, err + return result, err } result, err := sender.SendToPreferred([]*id.ID{firstGateway}, sendFunc, stop) @@ -151,14 +171,10 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message, //if the comm errors or the message fails to send, continue retrying. if err != nil { - if !strings.Contains(err.Error(), unrecoverableError) { - jww.ERROR.Printf("SendCmix failed to send to EphID %d (%s) on "+ - "round %d, trying a new round: %+v", ephID.Int64(), recipient, - bestRound.ID, err) - continue - } - - return 0, ephemeral.Id{}, err + jww.ERROR.Printf("SendCmix failed to send to EphID %d (%s) on "+ + "round %d, trying a new round: %+v", ephID.Int64(), recipient, + bestRound.ID, err) + continue } // Return if it sends properly diff --git a/network/message/sendCmixUtils.go b/network/message/sendCmixUtils.go index 28917e297a1d0f86e27c9dc2837567196bf1cfbd..4e71f5731f775386d9ab3afa1d10316214d67b32 100644 --- a/network/message/sendCmixUtils.go +++ b/network/message/sendCmixUtils.go @@ -28,8 +28,14 @@ import ( // Interface for SendCMIX comms; allows mocking this in testing. type sendCmixCommsInterface interface { - SendPutMessage(host *connect.Host, message *pb.GatewaySlot) (*pb.GatewaySlotResponse, error) - SendPutManyMessages(host *connect.Host, messages *pb.GatewaySlots) (*pb.GatewaySlotResponse, error) + // SendPutMessage places a cMix message on the gateway to be + // sent through cMix. + SendPutMessage(host *connect.Host, message *pb.GatewaySlot, + timeout time.Duration) (*pb.GatewaySlotResponse, error) + // SendPutManyMessages places a list of cMix messages on the gateway + // to be sent through cMix. + SendPutManyMessages(host *connect.Host, messages *pb.GatewaySlots, + timeout time.Duration) (*pb.GatewaySlotResponse, error) } // how much in the future a round needs to be to send to it @@ -41,15 +47,16 @@ const unrecoverableError = "failed with an unrecoverable error" // context. If the error is not among recoverable errors, then the recoverable // boolean will be returned false. If the error is among recoverable errors, // then the boolean will return true. +// recoverable means we should try resending to the round func handlePutMessageError(firstGateway *id.ID, instance *network.Instance, session *storage.Session, nodeRegistration chan network.NodeGateway, recipientString string, bestRound *pb.RoundInfo, - err error) (recoverable bool, returnErr error) { + err error) (returnErr error) { // If the comm errors or the message fails to send, then continue retrying; // otherwise, return if it sends properly if strings.Contains(err.Error(), "try a different round.") { - return false, errors.WithMessagef(err, "Failed to send to [%s] due to "+ + return errors.WithMessagef(err, "Failed to send to [%s] due to "+ "round error with round %d, bailing...", recipientString, bestRound.ID) } else if strings.Contains(err.Error(), "Could not authenticate client. "+ @@ -65,12 +72,12 @@ func handlePutMessageError(firstGateway *id.ID, instance *network.Instance, // Trigger go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID}) - return true, errors.WithMessagef(err, "Failed to send to [%s] via %s "+ + return errors.WithMessagef(err, "Failed to send to [%s] via %s "+ "due to failed authentication, retrying...", recipientString, firstGateway) } - return false, errors.WithMessage(err, "Failed to put cmix message") + return errors.WithMessage(err, "Failed to put cmix message") } diff --git a/network/message/sendCmix_test.go b/network/message/sendCmix_test.go index d3d7e116bd4eb5337628adccc87d422b0ae4b327..984e5cd2d01d4f4e687b72f6e8217c9440c980cd 100644 --- a/network/message/sendCmix_test.go +++ b/network/message/sendCmix_test.go @@ -56,9 +56,9 @@ func Test_attemptSendCmix(t *testing.T) { nid2 := id.NewIdFromString("jakexx360", id.Node, t) nid3 := id.NewIdFromString("westparkhome", id.Node, t) grp := cyclic.NewGroup(large.NewInt(7), large.NewInt(13)) - sess1.Cmix().Add(nid1, grp.NewInt(1)) - sess1.Cmix().Add(nid2, grp.NewInt(2)) - sess1.Cmix().Add(nid3, grp.NewInt(3)) + sess1.Cmix().Add(nid1, grp.NewInt(1), 0, nil) + sess1.Cmix().Add(nid2, grp.NewInt(2), 0, nil) + sess1.Cmix().Add(nid3, grp.NewInt(3), 0, nil) timestamps := []uint64{ uint64(now.Add(-30 * time.Second).UnixNano()), //PENDING diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go index e26dcb6de4ee21a8a72bcebf2dbf7ea26220453d..5f42729f651de44afa707d10b52d2d5c83caf428 100644 --- a/network/message/sendE2E.go +++ b/network/message/sendE2E.go @@ -90,8 +90,8 @@ func (m *Manager) SendE2E(msg message.Send, param params.E2E, //end to end encrypt the cmix message msgEnc := key.Encrypt(msgCmix) - jww.INFO.Printf("E2E sending %d/%d to %s with msgDigest: %s", - i+i, len(partitions), msg.Recipient, msgEnc.Digest()) + jww.INFO.Printf("E2E sending %d/%d to %s with msgDigest: %s, key fp: %s", + i+i, len(partitions), msg.Recipient, msgEnc.Digest(), key.Fingerprint()) //send the cmix message, each partition in its own thread wg.Add(1) diff --git a/network/message/sendManyCmix.go b/network/message/sendManyCmix.go index d9e098715a5032c90ed2bef7438f15ce2714f890..3c212f745846c2b60747517d3759a0d019e5528d 100644 --- a/network/message/sendManyCmix.go +++ b/network/message/sendManyCmix.go @@ -65,6 +65,8 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, stream := rng.GetStream() defer stream.Close() + maxTimeout := sender.GetHostParams().SendTimeout + recipientString, msgDigests := messageMapToStrings(msgs) jww.INFO.Printf("Looking for round to send cMix messages to [%s] "+ @@ -137,20 +139,19 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, } // Send the payload - sendFunc := func(host *connect.Host, target *id.ID) (interface{}, bool, error) { + sendFunc := func(host *connect.Host, target *id.ID) (interface{}, error) { wrappedMessage.Target = target.Marshal() - result, err := comms.SendPutManyMessages(host, wrappedMessage) + timeout := calculateSendTimeout(bestRound, maxTimeout) + result, err := comms.SendPutManyMessages(host, + wrappedMessage, timeout) if err != nil { - warn, err := handlePutMessageError(firstGateway, instance, + err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipientString, bestRound, err) - if warn { - jww.WARN.Printf("SendManyCMIX Failed: %+v", err) - } else { - return result, false, errors.WithMessagef(err, - "SendManyCMIX %s", unrecoverableError) - } + return result, errors.WithMessagef(err, + "SendManyCMIX %s", unrecoverableError) + } - return result, false, err + return result, err } result, err := sender.SendToPreferred([]*id.ID{firstGateway}, sendFunc, nil) diff --git a/network/message/sendManyCmix_test.go b/network/message/sendManyCmix_test.go index 1b0da6c083739765496e111628c5c3a74bdce05a..b787b3abc4445f5266a2cea51ced2429cdddba40 100644 --- a/network/message/sendManyCmix_test.go +++ b/network/message/sendManyCmix_test.go @@ -55,9 +55,9 @@ func Test_attemptSendManyCmix(t *testing.T) { nid2 := id.NewIdFromString("jakexx360", id.Node, t) nid3 := id.NewIdFromString("westparkhome", id.Node, t) grp := cyclic.NewGroup(large.NewInt(7), large.NewInt(13)) - sess1.Cmix().Add(nid1, grp.NewInt(1)) - sess1.Cmix().Add(nid2, grp.NewInt(2)) - sess1.Cmix().Add(nid3, grp.NewInt(3)) + sess1.Cmix().Add(nid1, grp.NewInt(1), 0, nil) + sess1.Cmix().Add(nid2, grp.NewInt(2), 0, nil) + sess1.Cmix().Add(nid3, grp.NewInt(3), 0, nil) timestamps := []uint64{ uint64(now.Add(-30 * time.Second).UnixNano()), // PENDING diff --git a/network/message/utils_test.go b/network/message/utils_test.go index 18284ad0bb6b6693db062f2fb5b048b5e18ca08f..80b31c9a911a49babe5862078fc864498f3fdb32 100644 --- a/network/message/utils_test.go +++ b/network/message/utils_test.go @@ -6,6 +6,7 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" "testing" + "time" ) type MockSendCMIXComms struct { @@ -32,14 +33,14 @@ func (mc *MockSendCMIXComms) RemoveHost(*id.ID) { } -func (mc *MockSendCMIXComms) SendPutMessage(*connect.Host, *mixmessages.GatewaySlot) (*mixmessages.GatewaySlotResponse, error) { +func (mc *MockSendCMIXComms) SendPutMessage(*connect.Host, *mixmessages.GatewaySlot, time.Duration) (*mixmessages.GatewaySlotResponse, error) { return &mixmessages.GatewaySlotResponse{ Accepted: true, RoundID: 3, }, nil } -func (mc *MockSendCMIXComms) SendPutManyMessages(*connect.Host, *mixmessages.GatewaySlots) (*mixmessages.GatewaySlotResponse, error) { +func (mc *MockSendCMIXComms) SendPutManyMessages(*connect.Host, *mixmessages.GatewaySlots, time.Duration) (*mixmessages.GatewaySlotResponse, error) { return &mixmessages.GatewaySlotResponse{ Accepted: true, RoundID: 3, @@ -100,6 +101,7 @@ func getNDF() *ndf.NetworkDefinition { ID: nodeId.Marshal(), Address: "0.0.0.0", TlsCertificate: "", + Status: ndf.Active, }, }, } diff --git a/network/node/register.go b/network/node/register.go index 2aa7a7d203a23cd800b202075f6ee169b1e2592e..a4424eb18b16b98f2b1766f68101fcf7c8fe11b4 100644 --- a/network/node/register.go +++ b/network/node/register.go @@ -8,9 +8,9 @@ package node import ( - "crypto/rand" "crypto/sha256" "fmt" + "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/network/gateway" @@ -26,18 +26,20 @@ import ( "gitlab.com/elixxir/crypto/registration" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" + "gitlab.com/xx_network/crypto/chacha" "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/tls" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" "strconv" + "sync" "time" ) type RegisterNodeCommsInterface interface { - SendRequestNonceMessage(host *connect.Host, - message *pb.NonceRequest) (*pb.Nonce, error) - SendConfirmNonceMessage(host *connect.Host, - message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error) + SendRequestClientKeyMessage(host *connect.Host, + message *pb.SignedClientKeyRequest) (*pb.SignedKeyResponse, error) } func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, @@ -45,10 +47,12 @@ func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen multi := stoppable.NewMulti("NodeRegistrations") + inProgess := &sync.Map{} + for i := uint(0); i < numParallel; i++ { stop := stoppable.NewSingle(fmt.Sprintf("NodeRegistration %d", i)) - go registerNodes(sender, session, rngGen, comms, stop, c) + go registerNodes(sender, session, rngGen, comms, stop, c, inProgess) multi.Add(stop) } @@ -57,7 +61,7 @@ func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen func registerNodes(sender *gateway.Sender, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface, - stop *stoppable.Single, c chan network.NodeGateway) { + stop *stoppable.Single, c chan network.NodeGateway, inProgress *sync.Map) { u := session.User() regSignature := u.GetTransmissionRegistrationValidationSignature() // Timestamp in which user has registered with registration @@ -75,8 +79,13 @@ func registerNodes(sender *gateway.Sender, session *storage.Session, stop.ToStopped() return case gw := <-c: + nidStr := fmt.Sprintf("%x", gw.Node.ID) + if _, operating := inProgress.LoadOrStore(nidStr, struct{}{}); operating { + continue + } err := registerWithNode(sender, comms, gw, regSignature, regTimestamp, uci, cmix, rng, stop) + inProgress.Delete(nidStr) if err != nil { jww.ERROR.Printf("Failed to register node: %+v", err) } @@ -98,13 +107,6 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface, return err } - gwid := ngw.Gateway.ID - gatewayID, err := id.Unmarshal(gwid) - if err != nil { - jww.ERROR.Println("registerWithNode() failed to decode gatewayID") - return err - } - if store.IsRegistered(nodeID) { return nil } @@ -112,6 +114,8 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface, jww.INFO.Printf("registerWithNode() begin registration with node: %s", nodeID) var transmissionKey *cyclic.Int + var validUntil uint64 + var keyId []byte // TODO: should move this to a precanned user initialization if uci.IsPrecanned() { userNum := int(uci.GetTransmissionID().Bytes()[7]) @@ -122,142 +126,159 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface, transmissionKey = store.GetGroup().NewIntFromBytes(h.Sum(nil)) jww.INFO.Printf("transmissionKey: %v", transmissionKey.Bytes()) } else { - // Initialise blake2b hash for transmission keys and reception - // keys - transmissionHash, _ := hash.NewCMixHash() - - nonce, dhPub, err := requestNonce(sender, comms, gatewayID, regSig, + // Request key from server + transmissionKey, keyId, validUntil, err = requestKey(sender, comms, ngw, regSig, registrationTimestampNano, uci, store, rng, stop) if err != nil { - return errors.Errorf("Failed to request nonce: %+v", err) + return errors.Errorf("Failed to request key: %+v", err) } - // Load server DH pubkey - serverPubDH := store.GetGroup().NewIntFromBytes(dhPub) - - // Confirm received nonce - jww.INFO.Printf("Register: Confirming received nonce from node %s", nodeID.String()) - err = confirmNonce(sender, comms, uci.GetTransmissionID().Bytes(), - nonce, uci.GetTransmissionRSA(), gatewayID, stop) - - if err != nil { - errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err) - return errors.New(errMsg) - } - transmissionKey = registration.GenerateBaseKey(store.GetGroup(), - serverPubDH, store.GetDHPrivateKey(), transmissionHash) } - store.Add(nodeID, transmissionKey) + store.Add(nodeID, transmissionKey, validUntil, keyId) jww.INFO.Printf("Completed registration with node %s", nodeID) return nil } -func requestNonce(sender *gateway.Sender, comms RegisterNodeCommsInterface, gwId *id.ID, - regSig []byte, registrationTimestampNano int64, uci *user.CryptographicIdentity, - store *cmix.Store, rng csprng.Source, stop *stoppable.Single) ([]byte, []byte, error) { +func requestKey(sender *gateway.Sender, comms RegisterNodeCommsInterface, + ngw network.NodeGateway, regSig []byte, registrationTimestampNano int64, + uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source, + stop *stoppable.Single) (*cyclic.Int, []byte, uint64, error) { dhPub := store.GetDHPublicKey().Bytes() + + // Reconstruct client confirmation message + userPubKeyRSA := rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic()) + confirmation := &pb.ClientRegistrationConfirmation{RSAPubKey: string(userPubKeyRSA), Timestamp: registrationTimestampNano} + confirmationSerialized, err := proto.Marshal(confirmation) + if err != nil { + return nil, nil, 0, err + } + + keyRequest := &pb.ClientKeyRequest{ + Salt: uci.GetTransmissionSalt(), + ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{ + RegistrarSignature: &messages.RSASignature{Signature: regSig}, + ClientRegistrationConfirmation: confirmationSerialized, + }, + ClientDHPubKey: dhPub, + RegistrationTimestamp: registrationTimestampNano, + RequestTimestamp: netTime.Now().UnixNano(), + } + + serializedMessage, err := proto.Marshal(keyRequest) + if err != nil { + return nil, nil, 0, err + } + opts := rsa.NewDefaultOptions() opts.Hash = hash.CMixHash - h, _ := hash.NewCMixHash() - h.Write(dhPub) + h := opts.Hash.New() + h.Write(serializedMessage) data := h.Sum(nil) // Sign DH pubkey clientSig, err := rsa.Sign(rng, uci.GetTransmissionRSA(), opts.Hash, data, opts) if err != nil { - return nil, nil, err + return nil, nil, 0, err + } + + gwid := ngw.Gateway.ID + gatewayID, err := id.Unmarshal(gwid) + if err != nil { + jww.ERROR.Println("registerWithNode() failed to decode gatewayID") + return nil, nil, 0, err } // Request nonce message from gateway - jww.INFO.Printf("Register: Requesting nonce from gateway %v", gwId.String()) + jww.INFO.Printf("Register: Requesting client key from gateway %v", gatewayID.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: regSig, - }, - ClientDHPubKey: dhPub, - RequestSignature: &messages.RSASignature{ - Signature: clientSig, - }, - Target: gwId.Marshal(), - // Timestamp in which user has registered with registration - TimeStamp: registrationTimestampNano, + keyResponse, err := comms.SendRequestClientKeyMessage(host, + &pb.SignedClientKeyRequest{ + ClientKeyRequest: serializedMessage, + ClientKeyRequestSignature: &messages.RSASignature{Signature: clientSig}, + Target: gatewayID.Bytes(), }) if err != nil { - errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err) - return nil, errors.New(errMsg) + return nil, errors.WithMessage(err, "Register: Failed requesting client key from gateway") } - if nonceResponse.Error != "" { - err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error)) - return nil, err + if keyResponse.Error != "" { + return nil, errors.WithMessage(err, "requestKey: clientKeyResponse error") } - return nonceResponse, nil + return keyResponse, nil }, stop) if err != nil { - return nil, nil, err + return nil, nil, 0, err } - nonceResponse := result.(*pb.Nonce) - // Use Client keypair to sign Server nonce - return nonceResponse.Nonce, nonceResponse.DHPubKey, nil -} + signedKeyResponse := result.(*pb.SignedKeyResponse) + if signedKeyResponse.Error != "" { + return nil, nil, 0, errors.New(signedKeyResponse.Error) + } -// 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(sender *gateway.Sender, comms RegisterNodeCommsInterface, UID, - nonce []byte, privateKeyRSA *rsa.PrivateKey, gwID *id.ID, - stop *stoppable.Single) error { - opts := rsa.NewDefaultOptions() - opts.Hash = hash.CMixHash - h, _ := hash.NewCMixHash() - h.Write(nonce) - // Hash the ID of the node we are sending to - nodeId := gwID.DeepCopy() - nodeId.SetType(id.Node) - h.Write(nodeId.Bytes()) - data := h.Sum(nil) + // Hash the response + h.Reset() + h.Write(signedKeyResponse.KeyResponse) + hashedResponse := h.Sum(nil) - // Hash nonce & sign - sig, err := rsa.Sign(rand.Reader, privateKeyRSA, opts.Hash, data, opts) + // Load node certificate + gatewayCert, err := tls.LoadCertificate(ngw.Gateway.TlsCertificate) if err != nil { - jww.ERROR.Printf( - "Register: Unable to sign nonce! %s", err) - return err + return nil, nil, 0, errors.WithMessagef(err, "Unable to load node's certificate") } - // Send signed nonce to Server - // TODO: This returns a receipt that can be used to speed up registration - msg := &pb.RequestRegistrationConfirmation{ - UserID: UID, - NonceSignedByClient: &messages.RSASignature{ - Signature: sig, - }, - Target: gwID.Marshal(), + // Extract public key + nodePubKey, err := tls.ExtractPublicKey(gatewayCert) + if err != nil { + return nil, nil, 0, errors.WithMessagef(err, "Unable to load node's public key") } - _, 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 - }, stop) + // Verify the response signature + err = rsa.Verify(nodePubKey, opts.Hash, hashedResponse, + signedKeyResponse.KeyResponseSignedByGateway.Signature, opts) + if err != nil { + return nil, nil, 0, errors.WithMessagef(err, "Could not verify node's signature") + } + + // Unmarshal the response + keyResponse := &pb.ClientKeyResponse{} + err = proto.Unmarshal(signedKeyResponse.KeyResponse, keyResponse) + if err != nil { + return nil, nil, 0, errors.WithMessagef(err, "Failed to unmarshal client key response") + } + + h.Reset() + + // Convert Node DH Public key to a cyclic.Int + grp := store.GetGroup() + nodeDHPub := grp.NewIntFromBytes(keyResponse.NodeDHPubKey) + + // Construct the session key + sessionKey := registration.GenerateBaseKey(grp, + nodeDHPub, store.GetDHPrivateKey(), h) - return err + // Verify the HMAC + h.Reset() + if !registration.VerifyClientHMAC(sessionKey.Bytes(), keyResponse.EncryptedClientKey, + h, keyResponse.EncryptedClientKeyHMAC) { + return nil, nil, 0, errors.WithMessagef(err, "Failed to verify client HMAC") + } + + // Decrypt the client key + clientKey, err := chacha.Decrypt(sessionKey.Bytes(), keyResponse.EncryptedClientKey) + if err != nil { + return nil, nil, 0, errors.WithMessagef(err, "Failed to decrypt client key") + } + + // Construct the transmission key from the client key + transmissionKey := store.GetGroup().NewIntFromBytes(clientKey) + + // Use Client keypair to sign Server nonce + return transmissionKey, keyResponse.KeyID, keyResponse.ValidUntil, nil } diff --git a/network/node/register_test.go b/network/node/register_test.go index 95d0765bfdfa47d2443ff45245d770d1eafaeb8b..7f89f4ed9cb4f849f41bd84bed1b4671234e8e84 100644 --- a/network/node/register_test.go +++ b/network/node/register_test.go @@ -39,23 +39,13 @@ func NewMockClientComms() *MockClientComms { func (mcc *MockClientComms) GetHost(hostId *id.ID) (*connect.Host, bool) { return &connect.Host{}, true } -func (mcc *MockClientComms) SendRequestNonceMessage(host *connect.Host, - message *pb.NonceRequest) (*pb.Nonce, error) { +func (mcc *MockClientComms) SendRequestClientKeyMessage(host *connect.Host, + message *pb.SignedClientKeyRequest) (*pb.SignedKeyResponse, error) { // Use this channel to confirm that request nonce was called mcc.request <- true - return &pb.Nonce{ - Nonce: []byte("nonce"), - DHPubKey: []byte("dhpub"), - }, nil -} -func (mcc *MockClientComms) SendConfirmNonceMessage(host *connect.Host, - message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error) { - // Use this channel to confirm that confirm nonce was called - mcc.confirm <- true - return &pb.RegistrationConfirmation{ - ClientSignedByServer: nil, - ClientGatewayKey: nil, - Error: "", + return &pb.SignedKeyResponse{ + KeyResponse: []byte("nonce"), + ClientGatewayKey: []byte("dhpub"), }, nil } diff --git a/network/roundTracking.go b/network/roundTracking.go new file mode 100644 index 0000000000000000000000000000000000000000..b0cf1bf9cfc61fccad3bb02f7aed7488c29bbbbe --- /dev/null +++ b/network/roundTracking.go @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2021 Privategrity Corporation / +// / +// All rights reserved. / +//////////////////////////////////////////////////////////////////////////////// + +// this is an in memory track of rounds that have been processed in this run of the +// xxdk. It only is enabled when loglevel is debug or higher. It will accumulate all +// rounds and then dump on exist. Is only enabled when run though the command line +// interface unless enabled explicitly in code. + +package network + +import ( + "fmt" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/xx_network/primitives/id" + "sort" + "sync" +) + +type RoundState uint8 + +const ( + Unchecked = iota + Unknown + NoMessageAvailable + MessageAvailable + Abandoned +) + +func (rs RoundState) String() string { + switch rs { + case Unchecked: + return "Unchecked" + case Unknown: + return "Unknown" + case MessageAvailable: + return "Message Available" + case NoMessageAvailable: + return "No Message Available" + case Abandoned: + return "Abandoned" + default: + return fmt.Sprintf("Unregistered Round State: %d", rs) + } +} + +type RoundTracker struct { + state map[id.Round]RoundState + mux sync.Mutex +} + +func NewRoundTracker() *RoundTracker { + return &RoundTracker{ + state: make(map[id.Round]RoundState), + } +} + +func (rt *RoundTracker) denote(rid id.Round, state RoundState) { + rt.mux.Lock() + defer rt.mux.Unlock() + // this ensures a lower state will not overwrite a higher state. + // eg. Unchecked does not overwrite MessageAvailable + if storedState, exists := rt.state[rid]; exists && storedState > state { + jww.TRACE.Printf("did not denote round %d because "+ + "stored state of %s (%d) > passed state %s (%d)", + rid, storedState, storedState, state, state) + return + } + rt.state[rid] = state +} + +func (rt *RoundTracker) String() string { + rt.mux.Lock() + defer rt.mux.Unlock() + jww.DEBUG.Printf("Debug Printing status of %d rounds", len(rt.state)) + keys := make([]int, 0, len(rt.state)) + for key := range rt.state { + keys = append(keys, int(key)) + } + + sort.Ints(keys) + + stringification := "" + for _, key := range keys { + stringification += fmt.Sprintf("Round: %d, state:%s \n", key, rt.state[id.Round(key)]) + } + + return stringification +} diff --git a/network/rounds/check.go b/network/rounds/check.go index 03cd83718495b71ef5455e9112dfe95fdda89fe3..3b9eb2d2849400d2fea2227373839cbc0def44e6 100644 --- a/network/rounds/check.go +++ b/network/rounds/check.go @@ -63,6 +63,12 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden jww.INFO.Printf("Messages found in round %d for %d (%s), looking "+ "up messages via historical lookup", roundID, identity.EphId.Int64(), identity.Source) + //store the round as an unretreived round + err = m.Session.UncheckedRounds().AddRound(roundID, nil, + identity.Source, identity.EphId) + if err != nil { + jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID) + } // If we didn't find it, send to Historical Rounds Retrieval m.historicalRounds <- historicalRoundRequest{ rid: roundID, @@ -73,6 +79,12 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden jww.INFO.Printf("Messages found in round %d for %d (%s), looking "+ "up messages via in ram lookup", roundID, identity.EphId.Int64(), identity.Source) + //store the round as an unretreived round + err = m.Session.UncheckedRounds().AddRound(roundID, ri, + identity.Source, identity.EphId) + if err != nil { + jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID) + } // If found, send to Message Retrieval Workers m.lookupRoundMessages <- roundLookup{ roundInfo: ri, diff --git a/network/rounds/remoteFilters_test.go b/network/rounds/remoteFilters_test.go index 73ce8476be59b4c814cb1a3d42ec8d6d8791db3d..6addedec8555e5bf7a55dec115813d8af0ac7993 100644 --- a/network/rounds/remoteFilters_test.go +++ b/network/rounds/remoteFilters_test.go @@ -118,7 +118,7 @@ func TestValidFilterRange(t *testing.T) { Identity: reception.Identity{ EphId: expectedEphID, Source: requestGateway, - StartValid: time.Now().Add(12 * time.Hour), + StartValid: time.Now().Add(-12 * time.Hour), EndValid: time.Now().Add(24 * time.Hour), }, } diff --git a/network/rounds/retrieve.go b/network/rounds/retrieve.go index caf5ce0e70d2b688a2045feb8c0dd21bcecdee6a..67809eb876ec94b3774ae530918f3ca23f487972 100644 --- a/network/rounds/retrieve.go +++ b/network/rounds/retrieve.go @@ -11,6 +11,7 @@ import ( "encoding/binary" "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/stoppable" "gitlab.com/elixxir/client/storage/reception" @@ -48,11 +49,10 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, case rl := <-m.lookupRoundMessages: ri := rl.roundInfo jww.DEBUG.Printf("Checking for messages in round %d", ri.ID) - err := m.Session.UncheckedRounds().AddRound(rl.roundInfo, - rl.identity.EphId, rl.identity.Source) + err := m.Session.UncheckedRounds().AddRound(id.Round(ri.ID), nil, + rl.identity.Source, rl.identity.EphId) if err != nil { - jww.ERROR.Printf("Could not add round %d in unchecked rounds store: %v", - rl.roundInfo.ID, err) + jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", id.Round(ri.ID)) } // Convert gateways in round to proper ID format @@ -120,7 +120,7 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms, if len(bundle.Messages) != 0 { jww.DEBUG.Printf("Removing round %d from unchecked store", ri.ID) - err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID)) + err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId) if err != nil { jww.ERROR.Printf("Could not remove round %d "+ "from unchecked rounds store: %v", ri.ID, err) @@ -143,7 +143,7 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, stop *stoppable.Single) (message.Bundle, error) { start := time.Now() // Send to the gateways using backup proxies - result, err := m.sender.SendToPreferred(gwIds, func(host *connect.Host, target *id.ID) (interface{}, bool, error) { + 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()) @@ -156,12 +156,18 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, // If the gateway doesnt have the round, return an error msgResp, err := comms.RequestMessages(host, msgReq) - if err == nil && !msgResp.GetHasRound() { - jww.INFO.Printf("No round error for round %d received from %s", roundID, target) - return message.Bundle{}, false, errors.Errorf(noRoundError, roundID) + + if err != nil { + //you need to default to a retryable errors because otherwise we cannot enumerate all errors + return nil, errors.WithMessage(err, gateway.RetryableError) + } + + if !msgResp.GetHasRound() { + errRtn := errors.Errorf(noRoundError, roundID) + return message.Bundle{}, errors.WithMessage(errRtn, gateway.RetryableError) } - return msgResp, false, err + return msgResp, nil }, stop) jww.INFO.Printf("Received message for round %d, processing...", roundID) // Fail the round if an error occurs so it can be tried again later @@ -172,13 +178,19 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, 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 + // of the bloom filters, false positives will happen sometimes msgs := msgResp.GetMessages() if msgs == nil || len(msgs) == 0 { jww.WARN.Printf("no messages for client %s "+ " in round %d. This happening every once in a while is normal,"+ " but can be indicative of a problem if it is consistent", m.TransmissionID, roundID) + + err = m.Session.UncheckedRounds().Remove(roundID, identity.Source, identity.EphId) + if err != nil { + jww.FATAL.Panicf("Failed to remove round %d: %+v", roundID, err) + } + return message.Bundle{}, nil } @@ -210,7 +222,7 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round, func (m *Manager) forceMessagePickupRetry(ri *pb.RoundInfo, rl roundLookup, comms messageRetrievalComms, gwIds []*id.ID, stop *stoppable.Single) (bundle message.Bundle, err error) { - rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID)) + rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId) if rnd.NumChecks == 0 { // Flip a coin to determine whether to pick up message stream := m.Rng.GetStream() diff --git a/network/rounds/unchecked.go b/network/rounds/unchecked.go index e62bff0c6885d71080b4544cf6602ec684fa2a9a..0618a95c75d07faa2bdc030348bb0c27b68fca84 100644 --- a/network/rounds/unchecked.go +++ b/network/rounds/unchecked.go @@ -11,6 +11,8 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage/reception" + "gitlab.com/elixxir/client/storage/rounds" + "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" "time" ) @@ -47,13 +49,33 @@ func (m *Manager) processUncheckedRounds(checkInterval time.Duration, backoffTab return case <-ticker.C: - // Pull and iterate through uncheckedRound list - roundList := m.Session.UncheckedRounds().GetList() - for rid, rnd := range roundList { + iterator := func(rid id.Round, rnd rounds.UncheckedRound) { + jww.DEBUG.Printf("checking if %d due for a message lookup", rid) // If this round is due for a round check, send the round over // to the retrieval thread. If not due, check next round. - if isRoundCheckDue(rnd.NumChecks, rnd.LastCheck, backoffTable) { - jww.INFO.Printf("Round %d due for a message lookup, retrying...", rid) + if !isRoundCheckDue(rnd.NumChecks, rnd.LastCheck, backoffTable) { + return + } + jww.INFO.Printf("Round %d due for a message lookup, retrying...", rid) + //check if it needs to be processed by historical Rounds + if rnd.Info == nil { + jww.INFO.Printf("Messages in round %d for %d (%s) loaded from unchecked rounds, looking "+ + "up messages via historical lookup", rnd.Id, rnd.EpdId.Int64(), + rnd.Source) + // If we didn't find it, send to Historical Rounds Retrieval + m.historicalRounds <- historicalRoundRequest{ + rid: rnd.Id, + identity: reception.IdentityUse{ + Identity: reception.Identity{ + EphId: rnd.EpdId, + Source: rnd.Source, + }, + }, + numAttempts: 0, + } + return + } else { + // Construct roundLookup object to send rl := roundLookup{ roundInfo: rnd.Info, @@ -73,15 +95,16 @@ func (m *Manager) processUncheckedRounds(checkInterval time.Duration, backoffTab } // Update the state of the round for next look-up (if needed) - err := uncheckedRoundStore.IncrementCheck(rid) + err := uncheckedRoundStore.IncrementCheck(rid, rnd.Source, rnd.EpdId) if err != nil { jww.ERROR.Printf("processUncheckedRounds error: Could not "+ "increment check attempts for round %d: %v", rid, err) } } - } + // Pull and iterate through uncheckedRound list + m.Session.UncheckedRounds().IterateOverList(iterator) } } } diff --git a/network/rounds/unchecked_test.go b/network/rounds/unchecked_test.go index 980ca1e6157a583cc239cab4332e358c5b04e84a..646af6e71598d835d03dd29ae168e12cb5bd8ac8 100644 --- a/network/rounds/unchecked_test.go +++ b/network/rounds/unchecked_test.go @@ -63,7 +63,7 @@ func TestUncheckedRoundScheduler(t *testing.T) { } // Add round ot check - err := testManager.Session.UncheckedRounds().AddRound(roundInfo, expectedEphID, requestGateway) + err := testManager.Session.UncheckedRounds().AddRound(roundId, roundInfo, requestGateway, expectedEphID) if err != nil { t.Fatalf("Could not add round to session: %v", err) } @@ -96,7 +96,8 @@ func TestUncheckedRoundScheduler(t *testing.T) { "\n\tReceived: %v", expectedEphID, testBundle.Identity.EphId) } - _, exists := testManager.Session.UncheckedRounds().GetRound(roundId) + _, exists := testManager.Session.UncheckedRounds().GetRound( + roundId, testBundle.Identity.Source, testBundle.Identity.EphId) if exists { t.Fatalf("Expected round %d to be removed after being processed", roundId) } diff --git a/network/rounds/utils_test.go b/network/rounds/utils_test.go index e4c41bbf3fe5c69e76ac2aeda3d8766a7f4aaf82..f323c266aa5744034fc369637ba1dcce217caa08 100644 --- a/network/rounds/utils_test.go +++ b/network/rounds/utils_test.go @@ -13,11 +13,13 @@ import ( "gitlab.com/elixxir/client/network/message" "gitlab.com/elixxir/client/storage" pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/comms/testkeys" "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" + "gitlab.com/xx_network/primitives/utils" "testing" "time" ) @@ -125,7 +127,24 @@ func newTestBackoffTable(face interface{}) [cappedTries]time.Duration { } func getNDF() *ndf.NetworkDefinition { + cert, _ := utils.ReadFile(testkeys.GetNodeCertPath()) + nodeID := id.NewIdFromBytes([]byte("gateway"), &testing.T{}) return &ndf.NetworkDefinition{ + Nodes: []ndf.Node{ + { + ID: nodeID.Bytes(), + Address: "", + TlsCertificate: string(cert), + Status: ndf.Active, + }, + }, + Gateways: []ndf.Gateway{ + { + ID: nodeID.Bytes(), + Address: "", + TlsCertificate: string(cert), + }, + }, E2E: ndf.Group{ Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" + "7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" + diff --git a/registration/register.go b/registration/register.go index 5a21fd97267ab0ae4880ceb9a996ddbdfb09dbd8..57b6cf8f2003b0323d96d9b53aff9dc6e0f1ac2c 100644 --- a/registration/register.go +++ b/registration/register.go @@ -8,42 +8,89 @@ package registration import ( + "github.com/golang/protobuf/proto" "github.com/pkg/errors" pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/crypto/registration" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/crypto/signature/rsa" ) func (perm *Registration) Register(transmissionPublicKey, receptionPublicKey *rsa.PublicKey, - registrationCode string) ([]byte, []byte, int64, error) { + registrationCode string) (transmissionSig []byte, receptionSig []byte, regTimestamp int64, err error) { return register(perm.comms, perm.host, transmissionPublicKey, receptionPublicKey, registrationCode) } // client.Comms should implement this interface type registrationMessageSender interface { - SendRegistrationMessage(host *connect.Host, message *pb.UserRegistration) (*pb.UserRegistrationConfirmation, error) + SendRegistrationMessage(host *connect.Host, message *pb.ClientRegistration) (*pb.SignedClientRegistrationConfirmations, error) } //register registers the user with optional registration code // Returns an error if registration fails. func register(comms registrationMessageSender, host *connect.Host, - transmissionPublicKey, receptionPublicKey *rsa.PublicKey, registrationCode string) ([]byte, []byte, int64, error) { + transmissionPublicKey, receptionPublicKey *rsa.PublicKey, + registrationCode string) ( + transmissionSig []byte, receptionSig []byte, regTimestamp int64, err error) { + // Send the message + transmissionPem := string(rsa.CreatePublicKeyPem(transmissionPublicKey)) + receptionPem := string(rsa.CreatePublicKeyPem(receptionPublicKey)) response, err := comms. SendRegistrationMessage(host, - &pb.UserRegistration{ - RegistrationCode: registrationCode, - ClientRSAPubKey: string(rsa.CreatePublicKeyPem(transmissionPublicKey)), - ClientReceptionRSAPubKey: string(rsa.CreatePublicKeyPem(receptionPublicKey)), + &pb.ClientRegistration{ + RegistrationCode: registrationCode, + ClientTransmissionRSAPubKey: transmissionPem, + ClientReceptionRSAPubKey: receptionPem, }) if err != nil { - err = errors.Wrap(err, "sendRegistrationMessage: Unable to contact Identity Server!") + err = errors.Wrap(err, "sendRegistrationMessage: Unable to "+ + "contact Identity Server!") return nil, nil, 0, err } if response.Error != "" { - return nil, nil, 0, errors.Errorf("sendRegistrationMessage: error handling message: %s", response.Error) + return nil, nil, 0, errors.Errorf("sendRegistrationMessage: "+ + "error handling message: %s", response.Error) } - return response.ClientSignedByServer.Signature, - response.ClientReceptionSignedByServer.Signature, response.Timestamp, nil + // Unmarshal reception confirmation + receptionConfirmation := &pb.ClientRegistrationConfirmation{} + err = proto.Unmarshal(response.GetClientReceptionConfirmation(). + ClientRegistrationConfirmation, receptionConfirmation) + if err != nil { + return nil, nil, 0, errors.WithMessage(err, "Failed to unmarshal "+ + "reception confirmation message") + } + + transmissionConfirmation := &pb.ClientRegistrationConfirmation{} + err = proto.Unmarshal(response.GetClientReceptionConfirmation(). + ClientRegistrationConfirmation, transmissionConfirmation) + if err != nil { + return nil, nil, 0, errors.WithMessage(err, "Failed to unmarshal "+ + "transmission confirmation message") + } + + // Verify reception signature + receptionSignature := response.GetClientReceptionConfirmation(). + GetRegistrarSignature().Signature + err = registration.VerifyWithTimestamp(host.GetPubKey(), + receptionConfirmation.Timestamp, receptionPem, + receptionSignature) + if err != nil { + return nil, nil, 0, errors.WithMessage(err, "Failed to verify reception signature") + } + + // Verify transmission signature + transmissionSignature := response.GetClientTransmissionConfirmation(). + GetRegistrarSignature().Signature + err = registration.VerifyWithTimestamp(host.GetPubKey(), + transmissionConfirmation.Timestamp, transmissionPem, + transmissionSignature) + if err != nil { + return nil, nil, 0, errors.WithMessage(err, "Failed to verify transmission signature") + } + + return transmissionSignature, + receptionSignature, + receptionConfirmation.Timestamp, nil } diff --git a/registration/register_test.go b/registration/register_test.go index 9840187ce77010144c0c0ccf8674f62a2c766354..d7b95baf4e4581ca96f56605e43eb043a628e46e 100644 --- a/registration/register_test.go +++ b/registration/register_test.go @@ -8,104 +8,198 @@ package registration import ( + "bytes" + "github.com/golang/protobuf/proto" "github.com/pkg/errors" pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/comms/testkeys" + "gitlab.com/elixxir/crypto/registration" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" - "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" - "reflect" + "gitlab.com/xx_network/primitives/utils" "testing" + "time" ) +var expectedSignatureOne = []byte{7, 15, 58, 201, 193, 112, 205, 247, 7, 200, 21, 185, 22, 82, 81, 114, 245, 179, 56, 157, 67, 209, 153, 59, 232, 119, 40, 84, 70, 246, 63, 211, 175, 190, 184, 152, 218, 74, 190, 232, 234, 106, 44, 249, 6, 86, 133, 191, 252, 74, 162, 114, 85, 211, 145, 41, 182, 33, 101, 86, 214, 106, 192, 8, 137, 153, 4, 17, 81, 202, 163, 117, 185, 75, 41, 5, 174, 50, 111, 234, 0, 94, 234, 105, 222, 74, 70, 225, 71, 81, 66, 203, 160, 128, 217, 93, 47, 132, 50, 40, 86, 115, 223, 200, 207, 103, 197, 35, 49, 82, 144, 142, 161, 104, 209, 163, 59, 19, 30, 132, 38, 91, 96, 21, 116, 200, 71, 108, 193, 68, 12, 33, 143, 146, 21, 6, 208, 222, 58, 91, 178, 217, 224, 168, 18, 222, 149, 165, 195, 1, 220, 63, 109, 153, 51, 151, 229, 158, 82, 172, 26, 67, 60, 128, 157, 64, 104, 131, 255, 88, 16, 208, 175, 211, 2, 221, 140, 200, 120, 169, 70, 142, 95, 183, 3, 213, 23, 125, 37, 157, 167, 88, 80, 25, 209, 184, 156, 91, 21, 242, 140, 250, 116, 227, 114, 214, 49, 98, 196, 58, 194, 9, 177, 223, 62, 88, 123, 14, 196, 224, 118, 247, 245, 103, 42, 239, 16, 170, 62, 255, 246, 244, 228, 1, 149, 146, 205, 47, 169, 21, 105, 0, 148, 137, 158, 170, 45, 16, 239, 179, 180, 120, 90, 131, 105, 16} +var expectedSignatureTwo = []byte{97, 206, 133, 26, 212, 226, 126, 58, 99, 225, 29, 219, 143, 47, 86, 153, 2, 43, 151, 157, 37, 150, 30, 81, 206, 141, 255, 164, 203, 254, 173, 35, 77, 150, 7, 208, 79, 82, 39, 163, 81, 230, 188, 149, 161, 54, 113, 241, 80, 97, 198, 225, 93, 130, 169, 46, 76, 115, 202, 101, 219, 201, 233, 60, 85, 181, 153, 153, 192, 56, 41, 119, 7, 211, 202, 245, 95, 150, 186, 162, 48, 77, 15, 192, 15, 196, 29, 68, 169, 212, 47, 46, 115, 242, 171, 86, 57, 170, 127, 23, 166, 36, 42, 174, 70, 73, 65, 255, 254, 199, 16, 165, 57, 77, 91, 145, 132, 180, 211, 123, 210, 161, 7, 114, 180, 130, 242, 52, 27, 211, 138, 163, 38, 233, 122, 102, 172, 217, 40, 99, 203, 255, 239, 147, 20, 249, 52, 109, 45, 106, 16, 41, 221, 45, 29, 125, 197, 42, 80, 167, 165, 82, 10, 54, 19, 114, 240, 127, 212, 126, 86, 125, 35, 142, 130, 172, 144, 7, 238, 215, 29, 105, 70, 171, 217, 161, 214, 26, 30, 201, 119, 191, 77, 81, 86, 118, 15, 180, 185, 20, 220, 236, 183, 67, 242, 255, 93, 16, 1, 31, 177, 211, 189, 231, 125, 83, 213, 65, 3, 209, 186, 70, 76, 51, 109, 153, 24, 81, 200, 57, 43, 8, 91, 24, 64, 118, 108, 237, 8, 204, 206, 95, 215, 72, 160, 42, 214, 133, 140, 86, 206, 0, 152, 139, 67, 234} + +func NewMockRegSender(key, cert []byte) (*MockRegistrationSender, error) { + privKey, err := rsa.LoadPrivateKeyFromPem(key) + if err != nil { + return nil, err + } + + // Generate a pre-canned time for consistent testing + testTime, err := time.Parse(time.RFC3339, + "2012-12-21T22:08:41+00:00") + if err != nil { + return nil, errors.Errorf("SignVerify error: "+ + "Could not parse precanned time: %v", err.Error()) + } + + h, err := connect.NewHost(&id.ClientRegistration, "address", cert, + connect.GetDefaultHostParams()) + if err != nil { + return nil, err + } + + return &MockRegistrationSender{ + privKey: privKey, + getHost: h, + prngOne: &CountingReader{count: 0}, + prngTwo: &CountingReader{count: 5}, + + mockTS: testTime, + }, nil +} + type MockRegistrationSender struct { - reg *pb.UserRegistration + reg *pb.ClientRegistration // param passed to SendRegistrationMessage - host *connect.Host + host *connect.Host + privKey *rsa.PrivateKey + prngOne *CountingReader + prngTwo *CountingReader + + mockTS time.Time // original host returned from GetHost getHost *connect.Host - succeedGetHost bool errSendRegistration error errInReply string } -func (s *MockRegistrationSender) SendRegistrationMessage(host *connect.Host, message *pb.UserRegistration) (*pb.UserRegistrationConfirmation, error) { - s.reg = message - s.host = host - return &pb.UserRegistrationConfirmation{ - ClientSignedByServer: &messages.RSASignature{ - Nonce: []byte("nonce"), - Signature: []byte("sig"), +func (s *MockRegistrationSender) SendRegistrationMessage(host *connect.Host, message *pb.ClientRegistration) (*pb.SignedClientRegistrationConfirmations, error) { + transSig, err := registration.SignWithTimestamp(s.prngOne, s.privKey, + s.mockTS.UnixNano(), message.ClientTransmissionRSAPubKey) + if err != nil { + return nil, errors.Errorf("Failed to sign transmission: %v", err) + } + + receptionSig, err := registration.SignWithTimestamp(s.prngTwo, s.privKey, + s.mockTS.UnixNano(), message.ClientReceptionRSAPubKey) + if err != nil { + return nil, errors.Errorf("Failed to sign reception: %v", err) + } + + transConfirmation := &pb.ClientRegistrationConfirmation{ + Timestamp: s.mockTS.UnixNano(), + RSAPubKey: message.ClientTransmissionRSAPubKey, + } + + receptionConfirmation := &pb.ClientRegistrationConfirmation{ + Timestamp: s.mockTS.UnixNano(), + RSAPubKey: message.ClientReceptionRSAPubKey, + } + + transConfirmationData, err := proto.Marshal(transConfirmation) + if err != nil { + return nil, err + } + + receptionConfirmationData, err := proto.Marshal(receptionConfirmation) + if err != nil { + return nil, err + } + + return &pb.SignedClientRegistrationConfirmations{ + ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{ + RegistrarSignature: &messages.RSASignature{ + Signature: transSig, + }, + ClientRegistrationConfirmation: transConfirmationData, }, - ClientReceptionSignedByServer: &messages.RSASignature{ - Nonce: []byte("receptionnonce"), - Signature: []byte("receptionsig"), + ClientReceptionConfirmation: &pb.SignedRegistrationConfirmation{ + RegistrarSignature: &messages.RSASignature{ + Signature: receptionSig, + }, + ClientRegistrationConfirmation: receptionConfirmationData, }, Error: s.errInReply, }, s.errSendRegistration } func (s *MockRegistrationSender) GetHost(*id.ID) (*connect.Host, bool) { - return s.getHost, s.succeedGetHost + return s.getHost, true } // Shows that we get expected result from happy path // Shows that registration gets RPCs with the correct parameters func TestRegisterWithPermissioning(t *testing.T) { - rng := csprng.NewSystemRNG() - key, err := rsa.GenerateKey(rng, 256) + + certData, err := utils.ReadFile(testkeys.GetNodeCertPath()) if err != nil { - t.Fatal(err) + t.Fatalf("Could not load certificate: %v", err) } - var sender MockRegistrationSender - sender.succeedGetHost = true - sender.getHost, err = connect.NewHost(&id.Permissioning, "address", nil, - connect.GetDefaultHostParams()) + keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath()) if err != nil { - t.Fatal(err) + t.Fatalf("Could not load private key: %v", err) } - regCode := "flooble doodle" - sig1, sig2, _, err := register(&sender, sender.getHost, key.GetPublic(), key.GetPublic(), regCode) + key, err := rsa.LoadPrivateKeyFromPem(keyData) if err != nil { - t.Error(err) + t.Fatalf("Could not load public key") } - if string(sig1) != "sig" { - t.Error("expected signature to be 'sig'") - } - if string(sig2) != "receptionsig" { - t.Error("expected signature to be 'receptionsig'") - } - if sender.host.String() != sender.getHost.String() { - t.Errorf("hosts differed. expected %v, got %v", sender.host, sender.getHost) + + sender, err := NewMockRegSender(keyData, certData) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) } - passedPub, err := rsa.LoadPublicKeyFromPem([]byte(sender.reg.ClientRSAPubKey)) + + regCode := "flooble doodle" + sig1, sig2, regTimestamp, err := register(sender, sender.getHost, key.GetPublic(), key.GetPublic(), regCode) if err != nil { - t.Error("failed to decode passed public key") t.Error(err) } - if !reflect.DeepEqual(passedPub, key.GetPublic()) { - t.Error("public keys different from expected") + + if regTimestamp != sender.mockTS.UnixNano() { + t.Fatalf("Unexpected timestamp returned from register: "+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", sender.mockTS.UnixNano(), regTimestamp) + } + + if !bytes.Equal(expectedSignatureOne, sig1) { + t.Fatalf("Unexpected signature one."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", expectedSignatureOne, sig1) } - if sender.reg.RegistrationCode != regCode { - t.Error("passed regcode different from expected") + + if !bytes.Equal(expectedSignatureTwo, sig2) { + t.Fatalf("Unexpected signature two."+ + "\n\tExpected: %v"+ + "\n\tReceived: %v", expectedSignatureTwo, sig2) } + } // Shows that returning an error from the registration server results in an // error from register func TestRegisterWithPermissioning_ResponseErr(t *testing.T) { - rng := csprng.NewSystemRNG() - key, err := rsa.GenerateKey(rng, 256) + certData, err := utils.ReadFile(testkeys.GetNodeCertPath()) + if err != nil { + t.Fatalf("Could not load certificate: %v", err) + } + + keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath()) if err != nil { - t.Fatal(err) + t.Fatalf("Could not load private key: %v", err) } - var sender MockRegistrationSender - sender.succeedGetHost = true + + key, err := rsa.LoadPrivateKeyFromPem(keyData) + if err != nil { + t.Fatalf("Could not load public key") + } + + sender, err := NewMockRegSender(keyData, certData) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) + } + sender.errInReply = "failure occurred on registration" - _, _, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "") + _, _, _, err = register(sender, nil, key.GetPublic(), key.GetPublic(), "") if err == nil { t.Error("no error if registration fails on registration") } @@ -114,16 +208,41 @@ func TestRegisterWithPermissioning_ResponseErr(t *testing.T) { // Shows that returning an error from the RPC (e.g. context deadline exceeded) // results in an error from register func TestRegisterWithPermissioning_ConnectionErr(t *testing.T) { - rng := csprng.NewSystemRNG() - key, err := rsa.GenerateKey(rng, 256) + certData, err := utils.ReadFile(testkeys.GetNodeCertPath()) + if err != nil { + t.Fatalf("Could not load certificate: %v", err) + } + + keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath()) + if err != nil { + t.Fatalf("Could not load private key: %v", err) + } + + key, err := rsa.LoadPrivateKeyFromPem(keyData) if err != nil { - t.Fatal(err) + t.Fatalf("Could not load public key") + } + + sender, err := NewMockRegSender(keyData, certData) + if err != nil { + t.Fatalf("Failed to create mock sender: %v", err) } - var sender MockRegistrationSender - sender.succeedGetHost = true sender.errSendRegistration = errors.New("connection problem") - _, _, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "") + _, _, _, err = register(sender, nil, key.GetPublic(), key.GetPublic(), "") if err == nil { t.Error("no error if e.g. context deadline exceeded") } } + +type CountingReader struct { + count uint8 +} + +// Read just counts until 254 then starts over again +func (c *CountingReader) Read(b []byte) (int, error) { + for i := 0; i < len(b); i++ { + c.count = (c.count + 1) % 255 + b[i] = c.count + } + return len(b), nil +} diff --git a/single/manager_test.go b/single/manager_test.go index ac5dd4253820f4fa8f2f50d5cdba079f0c94147c..a1658a0b733bb21df35e17deef53072a76f7cf21 100644 --- a/single/manager_test.go +++ b/single/manager_test.go @@ -186,6 +186,11 @@ func TestManager_StartProcesses_Stop(t *testing.T) { t.Errorf("Failed to close: %+v", err) } + // Wait for the stoppable to close + for !stop.IsStopped() { + time.Sleep(10 * time.Millisecond) + } + m.swb.(*switchboard.Switchboard).Speak(receiveMsg) timer := time.NewTimer(50 * time.Millisecond) @@ -287,6 +292,10 @@ func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Sing return nil, e2e.MessageID{}, time.Time{}, nil } +func (tnm *testNetworkManager) GetVerboseRounds() string { + return "" +} + func (tnm *testNetworkManager) SendUnsafe(_ message.Send, _ params.Unsafe) ([]id.Round, error) { return []id.Round{}, nil } diff --git a/single/receiveResponse.go b/single/receiveResponse.go index 831b25b358196ddc4806991610b51a0548ab56eb..8f09d8c8d7a98ff142eddaaef40e0bc5bd0a3369 100644 --- a/single/receiveResponse.go +++ b/single/receiveResponse.go @@ -18,6 +18,7 @@ import ( "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" + "strings" ) // receiveResponseHandler handles the reception of single-use response messages. @@ -40,10 +41,13 @@ func (m *Manager) receiveResponseHandler(rawMessages chan message.Receive, if err != nil { em := fmt.Sprintf("Failed to read single-use "+ "CMIX message response: %+v", err) - jww.WARN.Print(em) - if m.client != nil { - m.client.ReportEvent(9, "SingleUse", - "Error", em) + if strings.Contains(err.Error(), "no state exists for the reception ID") { + jww.TRACE.Print(em) + } else { + if m.client != nil { + m.client.ReportEvent(9, "SingleUse", + "Error", em) + } } } } diff --git a/single/reception.go b/single/reception.go index 23ca6f8bef891b8ae4426fe24172d4a88c2510c5..eca3bca2d2d5b093b0774d8e51b06ec9a763f4ed 100644 --- a/single/reception.go +++ b/single/reception.go @@ -108,5 +108,11 @@ func (m *Manager) processTransmission(msg format.Message, c := NewContact(payload.GetRID(transmitMsg.GetPubKey(grp)), transmitMsg.GetPubKey(grp), dhKey, payload.GetTagFP(), payload.GetMaxParts()) + jww.INFO.Printf("generated by singe use receiver reception id for single use: %s, "+ + "ephId: %v, pubkey: %x, msg: %s", + c.partner, "unknown:", + transmitMsg.GetPubKey(grp).Bytes(), + msg) + return payload.GetContents(), c, nil } diff --git a/single/response.go b/single/response.go index e847847b0f5f8206952550dc84d39b05c7e2be24..a5ab3cdb1baee704b214ff694b783c10182471ac 100644 --- a/single/response.go +++ b/single/response.go @@ -76,7 +76,7 @@ func (m *Manager) respondSingleUse(partner Contact, payload []byte, // Send Message round, ephID, err := m.net.SendCMIX(cmixMsgFunc, partner.partner, params.GetDefaultCMIX()) if err != nil { - jww.ERROR.Print("Failed to send single-use response CMIX "+ + jww.ERROR.Printf("Failed to send single-use response CMIX "+ "message part %d: %+v", j, err) } jww.DEBUG.Printf("Sending single-use response CMIX message part "+ diff --git a/single/transmission.go b/single/transmission.go index 25cd73276fb76eb2226ce7f44d1140cc0b9134b7..1b8bfddc05864b1ae790ba5af0193158f882bc8b 100644 --- a/single/transmission.go +++ b/single/transmission.go @@ -289,5 +289,8 @@ func makeIDs(msg *transmitMessagePayload, publicKey *cyclic.Int, jww.DEBUG.Printf("ephemeral.GetId(%s, %d, %d) = %d", rid, addressSize, timeNow.UnixNano(), ephID.Int64()) } + jww.INFO.Printf("generated by singe use sender reception id for single use: %s, "+ + "ephId: %d, pubkey: %x, msg: %s", rid, ephID.Int64(), publicKey.Bytes(), msg) + return rid, ephID, nil } diff --git a/single/transmitMessage.go b/single/transmitMessage.go index 768c47e2697dfe38c731ae0fbd2ffb7759442755..b3915fbe8ae43a87c3e0b7ce5fcff32e9c7adf12 100644 --- a/single/transmitMessage.go +++ b/single/transmitMessage.go @@ -9,6 +9,7 @@ package single import ( "encoding/binary" + "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/crypto/cyclic" @@ -246,3 +247,11 @@ func (mp transmitMessagePayload) SetContents(contents []byte) { copy(mp.contents, contents) } + +// String returns the contents for printing adhering to the stringer interface. +func (mp transmitMessagePayload) String() string { + return fmt.Sprintf("Data: %x [tagFP: %x, nonce: %x, "+ + "maxParts: %x, size: %x, content: %x]", mp.data, mp.tagFP, + mp.nonce, mp.maxParts, mp.size, mp.contents) + +} diff --git a/storage/cmix/key.go b/storage/cmix/key.go index 3ab6fc084cff007e879cd541afc44dbf910ea10d..6c2954ac4fa65f30b50cd771f73652357b7c19bb 100644 --- a/storage/cmix/key.go +++ b/storage/cmix/key.go @@ -8,6 +8,8 @@ package cmix import ( + "bytes" + "encoding/binary" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" @@ -18,17 +20,20 @@ import ( const currentKeyVersion = 0 type key struct { - kv *versioned.KV - k *cyclic.Int - - storeKey string + kv *versioned.KV + k *cyclic.Int + keyId []byte + validUntil uint64 + storeKey string } -func newKey(kv *versioned.KV, k *cyclic.Int, id *id.ID) *key { +func newKey(kv *versioned.KV, k *cyclic.Int, id *id.ID, validUntil uint64, keyId []byte) *key { nk := &key{ - kv: kv, - k: k, - storeKey: keyKey(id), + kv: kv, + k: k, + keyId: keyId, + validUntil: validUntil, + storeKey: keyKey(id), } if err := nk.save(); err != nil { @@ -89,15 +94,59 @@ func (k *key) delete(kv *versioned.KV, id *id.ID) { } } -// makes a binary representation of the given key in the keystore +// makes a binary representation of the given key and key values +// in the keystore func (k *key) marshal() ([]byte, error) { - return k.k.GobEncode() + buff := bytes.NewBuffer(nil) + keyBytes, err := k.k.GobEncode() + if err != nil { + return nil, err + } + + // Write key length + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(len(keyBytes))) + buff.Write(b) + + // Write key + buff.Write(keyBytes) + + // Write the keyId length + binary.LittleEndian.PutUint64(b, uint64(len(k.keyId))) + buff.Write(b) + + // Write keyID + buff.Write(k.keyId) + + // Write valid until + binary.LittleEndian.PutUint64(b, k.validUntil) + buff.Write(b) + + return buff.Bytes(), nil } // resets the data of the key from the binary representation of the key passed in func (k *key) unmarshal(b []byte) error { + buff := bytes.NewBuffer(b) + + // Get the key length + keyLen := int(binary.LittleEndian.Uint64(buff.Next(8))) + + // Decode the key length k.k = &cyclic.Int{} - return k.k.GobDecode(b) + err := k.k.GobDecode(buff.Next(keyLen)) + if err != nil { + return err + } + + // Get the keyID length + keyIDLen := int(binary.LittleEndian.Uint64(buff.Next(8))) + k.keyId = buff.Next(keyIDLen) + + // Get the valid until value + k.validUntil = binary.LittleEndian.Uint64(buff.Next(8)) + + return nil } // Adheres to the stringer interface diff --git a/storage/cmix/store.go b/storage/cmix/store.go index 95a67d7789c3f8f35642e96ef9d81cc864f4b0cb..32b7d506c7de63b4fa901773ff6ba5551747d293 100644 --- a/storage/cmix/store.go +++ b/storage/cmix/store.go @@ -34,10 +34,11 @@ type Store struct { nodes map[id.ID]*key dhPrivateKey *cyclic.Int dhPublicKey *cyclic.Int - - grp *cyclic.Group - kv *versioned.KV - mux sync.RWMutex + validUntil uint64 + keyId []byte + grp *cyclic.Group + kv *versioned.KV + mux sync.RWMutex } // NewStore returns a new cMix storage object. @@ -98,11 +99,12 @@ func LoadStore(kv *versioned.KV) (*Store, error) { // Add adds the key for a round to the cMix storage object. Saves the updated // list of nodes and the key to disk. -func (s *Store) Add(nid *id.ID, k *cyclic.Int) { +func (s *Store) Add(nid *id.ID, k *cyclic.Int, + validUntil uint64, keyId []byte) { s.mux.Lock() defer s.mux.Unlock() - nodeKey := newKey(s.kv, k, nid) + nodeKey := newKey(s.kv, k, nid, validUntil, keyId) s.nodes[*nid] = nodeKey if err := s.save(); err != nil { diff --git a/storage/cmix/store_test.go b/storage/cmix/store_test.go index 63329b769b1706dc4a341988a03e28fe84a37642..d84b2dff66723020d5c0b16f7b7d2b0d059911d1 100644 --- a/storage/cmix/store_test.go +++ b/storage/cmix/store_test.go @@ -8,6 +8,7 @@ package cmix import ( + "bytes" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/diffieHellman" @@ -16,6 +17,7 @@ import ( "gitlab.com/xx_network/crypto/large" "gitlab.com/xx_network/primitives/id" "testing" + "time" ) // Happy path @@ -57,9 +59,9 @@ func TestStore_AddRemove(t *testing.T) { testStore, _ := makeTestStore() nodeId := id.NewIdFromString("test", id.Node, t) - key := testStore.grp.NewInt(5) - - testStore.Add(nodeId, key) + k := testStore.grp.NewInt(5) + keyId := []byte("keyId") + testStore.Add(nodeId, k, 0, keyId) if _, exists := testStore.nodes[*nodeId]; !exists { t.Fatal("Failed to add node key") } @@ -80,7 +82,7 @@ func TestStore_AddHas(t *testing.T) { nodeId := id.NewIdFromString("test", id.Node, t) key := testStore.grp.NewInt(5) - testStore.Add(nodeId, key) + testStore.Add(nodeId, key, 0, nil) if _, exists := testStore.nodes[*nodeId]; !exists { t.Fatal("Failed to add node key") } @@ -113,9 +115,17 @@ func TestLoadStore(t *testing.T) { // Add a test node key nodeId := id.NewIdFromString("test", id.Node, t) - key := testStore.grp.NewInt(5) + k := testStore.grp.NewInt(5) + testTime, err := time.Parse(time.RFC3339, + "2012-12-21T22:08:41+00:00") + if err != nil { + t.Fatalf("Could not parse precanned time: %v", err.Error()) + } + expectedValid := uint64(testTime.UnixNano()) - testStore.Add(nodeId, key) + expectedKeyId := []byte("expectedKeyID") + + testStore.Add(nodeId, k, uint64(expectedValid), expectedKeyId) // Load the store and check its attributes store, err := LoadStore(kv) @@ -131,6 +141,18 @@ func TestLoadStore(t *testing.T) { if len(store.nodes) != len(testStore.nodes) { t.Errorf("LoadStore failed to load node keys") } + + circuit := connect.NewCircuit([]*id.ID{nodeId}) + keys, _ := store.GetRoundKeys(circuit) + if keys.keys[0].validUntil != expectedValid { + t.Errorf("Unexpected valid until value loaded from store."+ + "\n\tExpected: %v\n\tReceived: %v", expectedValid, keys.keys[0].validUntil) + } + if !bytes.Equal(keys.keys[0].keyId, expectedKeyId) { + t.Errorf("Unexpected keyID value loaded from store."+ + "\n\tExpected: %v\n\tReceived: %v", expectedKeyId, keys.keys[0].keyId) + } + } // Happy path @@ -145,7 +167,7 @@ func TestStore_GetRoundKeys(t *testing.T) { for i := 0; i < numIds; i++ { nodeIds[i] = id.NewIdFromUInt(uint64(i)+1, id.Node, t) key := testStore.grp.NewInt(int64(i) + 1) - testStore.Add(nodeIds[i], key) + testStore.Add(nodeIds[i], key, 0, nil) // This is wack but it cleans up after the test defer testStore.Remove(nodeIds[i]) @@ -176,8 +198,8 @@ func TestStore_GetRoundKeys_Missing(t *testing.T) { // Only add every other node so there are missing nodes if i%2 == 0 { - testStore.Add(nodeIds[i], key) - testStore.Add(nodeIds[i], key) + testStore.Add(nodeIds[i], key, 0, nil) + testStore.Add(nodeIds[i], key, 0, nil) // This is wack but it cleans up after the test defer testStore.Remove(nodeIds[i]) @@ -211,7 +233,7 @@ func TestStore_Count(t *testing.T) { count := 50 for i := 0; i < count; i++ { - store.Add(id.NewIdFromUInt(uint64(i), id.Node, t), grp.NewInt(int64(42+i))) + store.Add(id.NewIdFromUInt(uint64(i), id.Node, t), grp.NewInt(int64(42+i)), 0, nil) } if store.Count() != count { diff --git a/storage/e2e/stateVector.go b/storage/e2e/stateVector.go index 10306ae562bde79a5d6382d167f83b823ccbc8df..fea42c68cec3475ba27d1e6a16f884c7b8b73463 100644 --- a/storage/e2e/stateVector.go +++ b/storage/e2e/stateVector.go @@ -212,17 +212,16 @@ func (sv *stateVector) Delete() error { // execute a store and a store must be executed after. func (sv *stateVector) nextAvailable() { - block := (sv.firstAvailable + 1) / 64 - pos := (sv.firstAvailable + 1) % 64 + //plus one so we start at the next one + pos := sv.firstAvailable + 1 + block := pos / 64 - for ; block < uint32(len(sv.vect)) && (sv.vect[block]>>pos)&1 == 1; pos++ { - if pos == 63 { - pos = 0 - block++ - } + for block < uint32(len(sv.vect)) && (sv.vect[block]>>(pos%64))&1 == 1 { + pos++ + block = pos / 64 } - sv.firstAvailable = block*64 + pos + sv.firstAvailable = pos } //ekv functions diff --git a/storage/e2e/stateVector_test.go b/storage/e2e/stateVector_test.go index 99fd5cf606088b3d55ddda3f3e6045a04effe9af..e67d251c3c07c4f6ef3014080dee93a88a7846e4 100644 --- a/storage/e2e/stateVector_test.go +++ b/storage/e2e/stateVector_test.go @@ -11,6 +11,7 @@ import ( "fmt" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/ekv" + "math" "math/bits" "reflect" "testing" @@ -110,6 +111,31 @@ func TestStateVector_Next(t *testing.T) { } } +// Shows that Next mutates vector state as expected +// Shows that Next can find key indexes all throughout the bitfield +// A bug was found when the next avalible was in the first index of a word, this tests that case +func TestStateVector_Next_EdgeCase(t *testing.T) { + // Expected results: all keynums, and beyond the last key + //expectedFirstAvail := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + const numKeys = 1000 + sv, err := newStateVector(versioned.NewKV(make(ekv.Memstore)), "key", numKeys) + if err != nil { + t.Fatal(err) + } + + // Set a few clean bits randomly + sv.vect[0] = math.MaxUint64 + + sv.firstAvailable = 0 + sv.nextAvailable() + + // firstAvailable should now be beyond the end of the bitfield + if sv.firstAvailable != 64 { + t.Errorf("Next avalivle skiped the first of the next word, should be 64, is %d", sv.firstAvailable) + } +} + // Shows that Use() mutates the state vector itself func TestStateVector_Use(t *testing.T) { // These keyNums will be set to dirty with Use diff --git a/storage/rounds/checkedRounds.go b/storage/rounds/checkedRounds.go index a7e3ebdecd425a3ba97902a2197fd3189b7b6f59..df17faa3ad38c7ea84d67e44aee01feb92230925 100644 --- a/storage/rounds/checkedRounds.go +++ b/storage/rounds/checkedRounds.go @@ -52,7 +52,7 @@ func newCheckedRounds(maxRounds int, store *utility.BlockStore) *CheckedRounds { return &CheckedRounds{ m: make(map[id.Round]interface{}), l: list.New(), - recent: []id.Round{}, + recent: make([]id.Round, 0, maxRounds), store: store, maxRounds: maxRounds, } diff --git a/storage/rounds/earliestRound.go b/storage/rounds/earliestRound.go index 456e11c146e1998e45c3e9b814d5f718ddf99807..a072419f389d84544ff6029062d5839f0e2b0294 100644 --- a/storage/rounds/earliestRound.go +++ b/storage/rounds/earliestRound.go @@ -71,16 +71,19 @@ func (ur *EarliestRound) save() { } } -func (ur *EarliestRound) Set(rid id.Round) (id.Round, bool) { +// Set returns the updated earliest round, the old earliest round, and if they are changed. +// Updates the earliest round if it is newer than stored one +func (ur *EarliestRound) Set(rid id.Round) (id.Round, id.Round, bool) { ur.mux.Lock() defer ur.mux.Unlock() changed := false + old := ur.rid if rid > ur.rid { changed = true ur.rid = rid ur.save() } - return ur.rid, changed + return ur.rid, old, changed } func (ur *EarliestRound) Get() id.Round { diff --git a/storage/rounds/roundIdentity.go b/storage/rounds/roundIdentity.go new file mode 100644 index 0000000000000000000000000000000000000000..4148ec014eaa238a2d40af1ee51602f7453aa910 --- /dev/null +++ b/storage/rounds/roundIdentity.go @@ -0,0 +1,50 @@ +package rounds + +import ( + "encoding/base64" + "encoding/binary" + "gitlab.com/elixxir/crypto/hash" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" +) + +// roundIdentitySize is the size of a roundIdentity. +const roundIdentitySize = 32 + +// roundIdentity uniquely identifies a round ID for a specific identity. +type roundIdentity [roundIdentitySize]byte + +// newRoundIdentity generates a new unique round identifier for the round ID, +// recipient ID, and ephemeral ID. +func newRoundIdentity(rid id.Round, recipient *id.ID, ephID ephemeral.Id) roundIdentity { + h, _ := hash.NewCMixHash() + ridBytes := make([]byte, 8) + binary.BigEndian.PutUint64(ridBytes, uint64(rid)) + h.Write(ridBytes) + h.Write(recipient[:]) + h.Write(ephID[:]) + riBytes := h.Sum(nil) + + ri := unmarshalRoundIdentity(riBytes) + + return ri +} + +// String prints a base 64 string representation of roundIdentity. This function +// satisfies the fmt.Stringer interface. +func (ri roundIdentity) String() string { + return base64.StdEncoding.EncodeToString(ri[:]) +} + +// Marshal returns the roundIdentity as a byte slice. +func (ri roundIdentity) Marshal() []byte { + return ri[:] +} + +// unmarshalRoundIdentity unmarshalls the byte slice into a roundIdentity. +func unmarshalRoundIdentity(b []byte) roundIdentity { + var ri roundIdentity + copy(ri[:], b) + + return ri +} diff --git a/storage/rounds/uncheckedRounds.go b/storage/rounds/uncheckedRounds.go index 898222b71556ef86146d996e23c9804bff356f85..aa5e57497d36c21f970d3dcb599708ba78bf5650 100644 --- a/storage/rounds/uncheckedRounds.go +++ b/storage/rounds/uncheckedRounds.go @@ -18,34 +18,40 @@ import ( "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/netTime" "sync" + "testing" "time" ) const ( uncheckedRoundVersion = 0 + roundInfoVersion = 0 uncheckedRoundPrefix = "uncheckedRoundPrefix" + roundKeyPrefix = "roundInfo:" + // Key to store rounds uncheckedRoundKey = "uncheckRounds" - // Key to store individual round + // Housekeeping constant (used for serializing uint64 ie id.Round) uint64Size = 8 - // Maximum checks that can be performed on a round. Intended so that - // a round is checked no more than 1 week approximately (network/rounds.cappedTries + 7) + + // Maximum checks that can be performed on a round. Intended so that a round + // is checked no more than 1 week approximately (network/rounds.cappedTries + 7) maxChecks = 14 ) -// Round identity information used in message retrieval -// Derived from reception.Identity saving data needed -// for message retrieval +// Identity contains round identity information used in message retrieval. +// Derived from reception.Identity saving data needed for message retrieval. type Identity struct { EpdId ephemeral.Id Source *id.ID } -// Unchecked round structure is rounds which failed on message retrieval -// These rounds are stored for retry of message retrieval +// UncheckedRound contains rounds that failed on message retrieval. These rounds +// are stored for retry of message retrieval. type UncheckedRound struct { Info *pb.RoundInfo + Id id.Round + Identity // Timestamp in which round has last been checked LastCheck time.Time @@ -53,17 +59,21 @@ type UncheckedRound struct { NumChecks uint64 } -// marshal serializes UncheckedRound r into a byte slice -func (r UncheckedRound) marshal() ([]byte, error) { +// marshal serializes UncheckedRound r into a byte slice. +func (r UncheckedRound) marshal(kv *versioned.KV) ([]byte, error) { buf := bytes.NewBuffer(nil) - // Write the round info + // Store teh round info + if r.Info != nil { + if err := storeRoundInfo(kv, r.Info, r.Source, r.EpdId); err != nil { + return nil, errors.WithMessagef(err, + "failed to marshal unchecked rounds") + } + } + + // Marshal the round ID b := make([]byte, uint64Size) - infoBytes, err := proto.Marshal(r.Info) - binary.LittleEndian.PutUint64(b, uint64(len(infoBytes))) + binary.LittleEndian.PutUint64(b, uint64(r.Id)) buf.Write(b) - buf.Write(infoBytes) - - b = make([]byte, uint64Size) // Write the round identity info buf.Write(r.Identity.EpdId[:]) @@ -91,60 +101,57 @@ func (r UncheckedRound) marshal() ([]byte, error) { return buf.Bytes(), nil } -// unmarshal deserializes round data from buff into UncheckedRound r -func (r *UncheckedRound) unmarshal(buff *bytes.Buffer) error { +// unmarshal deserializes round data from buff into UncheckedRound r. +func (r *UncheckedRound) unmarshal(kv *versioned.KV, buff *bytes.Buffer) error { // Deserialize the roundInfo - roundInfoLen := binary.LittleEndian.Uint64(buff.Next(uint64Size)) - roundInfoBytes := buff.Next(int(roundInfoLen)) - ri := &pb.RoundInfo{} - if err := proto.Unmarshal(roundInfoBytes, ri); err != nil { - return errors.WithMessagef(err, "Failed to unmarshal roundInfo") - } - r.Info = ri + r.Id = id.Round(binary.LittleEndian.Uint64(buff.Next(uint64Size))) // Deserialize the round identity information copy(r.EpdId[:], buff.Next(uint64Size)) sourceId, err := id.Unmarshal(buff.Next(id.ArrIDLen)) if err != nil { - return errors.WithMessage(err, "Failed to unmarshal round identity.source") + return errors.WithMessagef(err, + "Failed to unmarshal round identity.source of %d", r.Id) } r.Source = sourceId // Deserialize the timestamp bytes timestampLen := binary.LittleEndian.Uint64(buff.Next(uint64Size)) - tsByes := buff.Next(int(uint64(timestampLen))) + tsByes := buff.Next(int(timestampLen)) if err = r.LastCheck.UnmarshalBinary(tsByes); err != nil { - return errors.WithMessage(err, "Failed to unmarshal round timestamp") + return errors.WithMessagef(err, + "Failed to unmarshal round timestamp of %d", r.Id) } r.NumChecks = binary.LittleEndian.Uint64(buff.Next(uint64Size)) + r.Info, _ = loadRoundInfo(kv, r.Id, r.Source, r.EpdId) + return nil } -// Storage object saving rounds to retry for message retrieval +// UncheckedRoundStore stores rounds to retry for message retrieval. type UncheckedRoundStore struct { - list map[id.Round]UncheckedRound + list map[roundIdentity]UncheckedRound mux sync.RWMutex kv *versioned.KV } -// Constructor for a UncheckedRoundStore +// NewUncheckedStore is a constructor for a UncheckedRoundStore. func NewUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { kv = kv.Prefix(uncheckedRoundPrefix) urs := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound, 0), + list: make(map[roundIdentity]UncheckedRound, 0), kv: kv, } return urs, urs.save() - } -// Loads an deserializes a UncheckedRoundStore from memory +// LoadUncheckedStore loads a deserializes a UncheckedRoundStore from memory. func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { kv = kv.Prefix(uncheckedRoundPrefix) @@ -154,7 +161,7 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { } urs := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound), + list: make(map[roundIdentity]UncheckedRound), kv: kv, } @@ -166,13 +173,16 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) { return urs, err } -// Adds a round to check on the list and saves to memory -func (s *UncheckedRoundStore) AddRound(ri *pb.RoundInfo, ephID ephemeral.Id, source *id.ID) error { +// AddRound adds a round to check on the list and saves to memory. +func (s *UncheckedRoundStore) AddRound(rid id.Round, ri *pb.RoundInfo, + source *id.ID, ephID ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() - rid := id.Round(ri.ID) + roundId := newRoundIdentity(rid, source, ephID) + + stored, exists := s.list[roundId] - if _, exists := s.list[rid]; !exists { + if !exists || stored.Info == nil { newUncheckedRound := UncheckedRound{ Info: ri, Identity: Identity{ @@ -183,42 +193,55 @@ func (s *UncheckedRoundStore) AddRound(ri *pb.RoundInfo, ephID ephemeral.Id, sou NumChecks: 0, } - s.list[rid] = newUncheckedRound - + s.list[roundId] = newUncheckedRound return s.save() } return nil } -// Retrieves an UncheckedRound from the map, if it exists -func (s *UncheckedRoundStore) GetRound(rid id.Round) (UncheckedRound, bool) { +// GetRound retrieves an UncheckedRound from the map, if it exists. +func (s *UncheckedRoundStore) GetRound(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) (UncheckedRound, bool) { s.mux.RLock() defer s.mux.RUnlock() - rnd, exists := s.list[rid] + rnd, exists := s.list[newRoundIdentity(rid, recipient, ephId)] return rnd, exists } -// Retrieves the list of rounds -func (s *UncheckedRoundStore) GetList() map[id.Round]UncheckedRound { +func (s *UncheckedRoundStore) GetList(*testing.T) map[roundIdentity]UncheckedRound { s.mux.RLock() defer s.mux.RUnlock() return s.list } -// Increments the amount of checks performed on this stored round -func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error { +// IterateOverList retrieves the list of rounds. +func (s *UncheckedRoundStore) IterateOverList(iterator func(rid id.Round, + rnd UncheckedRound)) { + s.mux.RLock() + defer s.mux.RUnlock() + + for _, rnd := range s.list { + go iterator(rnd.Id, rnd) + } +} + +// IncrementCheck increments the amount of checks performed on this stored +// round. +func (s *UncheckedRoundStore) IncrementCheck(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() - rnd, exists := s.list[rid] + nri := newRoundIdentity(rid, recipient, ephId) + rnd, exists := s.list[nri] if !exists { return errors.Errorf("round %d could not be found in RAM", rid) } - // If a round has been checked the maximum amount of times, - // we bail the round by removing it from store and no longer checking + // If a round has been checked the maximum amount of times, then bail the + // round by removing it from store and no longer checking if rnd.NumChecks >= maxChecks { - if err := s.remove(rid); err != nil { + if err := s.remove(rid, rnd.Identity.Source, ephId); err != nil { return errors.WithMessagef(err, "Round %d reached maximum checks "+ "but could not be removed", rid) } @@ -228,33 +251,56 @@ func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error { // Update the rounds state rnd.LastCheck = netTime.Now() rnd.NumChecks++ - s.list[rid] = rnd + s.list[nri] = rnd return s.save() } -// Remove deletes a round from UncheckedRoundStore's list and from storage -func (s *UncheckedRoundStore) Remove(rid id.Round) error { +// Remove deletes a round from UncheckedRoundStore's list and from storage. +func (s *UncheckedRoundStore) Remove(rid id.Round, source *id.ID, + ephId ephemeral.Id) error { s.mux.Lock() defer s.mux.Unlock() - return s.remove(rid) + return s.remove(rid, source, ephId) } -// Remove is a helper function which removes the round from UncheckedRoundStore's list -// Note this method is unsafe and should only be used by methods with a lock -func (s *UncheckedRoundStore) remove(rid id.Round) error { - if _, exists := s.list[rid]; !exists { +// Remove is a helper function which removes the round from +// UncheckedRoundStore's list. Note that this method is unsafe and should only +// be used by methods with a lock. +func (s *UncheckedRoundStore) remove(rid id.Round, recipient *id.ID, + ephId ephemeral.Id) error { + roundId := newRoundIdentity(rid, recipient, ephId) + ur, exists := s.list[roundId] + if !exists { return errors.Errorf("round %d does not exist in store", rid) } - delete(s.list, rid) - return s.save() + + delete(s.list, roundId) + if err := s.save(); err != nil { + return errors.WithMessagef(err, + "Failed to delete round %d from unchecked round store", rid) + } + + // Do not delete round infos if none exist + if ur.Info == nil { + return nil + } + + if err := deleteRoundInfo(s.kv, rid, recipient, ephId); err != nil { + return errors.WithMessagef(err, + "Failed to delete round %d's roundinfo from unchecked round store, "+ + "round itself deleted. This is a storage leak", rid) + } + + return nil } -// save stores the information from the round list into storage +// save stores the information from the round list into storage. func (s *UncheckedRoundStore) save() error { // Store list of rounds data, err := s.marshal() if err != nil { - return errors.WithMessagef(err, "Could not marshal data for unchecked rounds") + return errors.WithMessagef(err, + "Could not marshal data for unchecked rounds") } // Create the versioned object @@ -267,24 +313,27 @@ func (s *UncheckedRoundStore) save() error { // Save to storage err = s.kv.Set(uncheckedRoundKey, uncheckedRoundVersion, obj) if err != nil { - return errors.WithMessagef(err, "Could not store data for unchecked rounds") + return errors.WithMessagef(err, + "Could not store data for unchecked rounds") } return nil } -// marshal is a helper function which serializes all rounds in list to bytes +// marshal is a helper function which serializes all rounds in list to bytes. func (s *UncheckedRoundStore) marshal() ([]byte, error) { buf := bytes.NewBuffer(nil) + // Write number of rounds the buffer b := make([]byte, 8) - binary.PutVarint(b, int64(len(s.list))) + binary.BigEndian.PutUint32(b, uint32(len(s.list))) buf.Write(b) for rid, rnd := range s.list { - rndData, err := rnd.marshal() + rndData, err := rnd.marshal(s.kv) if err != nil { - return nil, errors.WithMessagef(err, "Failed to marshal round %d", rid) + return nil, errors.WithMessagef(err, + "Failed to marshal round %d", rid) } buf.Write(rndData) @@ -294,21 +343,68 @@ func (s *UncheckedRoundStore) marshal() ([]byte, error) { return buf.Bytes(), nil } -// unmarshal deserializes an UncheckedRound from its stored byte data +// unmarshal deserializes an UncheckedRound from its stored byte data. func (s *UncheckedRoundStore) unmarshal(data []byte) error { buff := bytes.NewBuffer(data) + // Get number of rounds in list - length, _ := binary.Varint(buff.Next(8)) + length := binary.BigEndian.Uint32(buff.Next(8)) for i := 0; i < int(length); i++ { rnd := UncheckedRound{} - err := rnd.unmarshal(buff) + err := rnd.unmarshal(s.kv, buff) if err != nil { - return errors.WithMessage(err, "Failed to unmarshal rounds in storage") + return errors.WithMessage(err, + "Failed to unmarshal rounds in storage") } - s.list[id.Round(rnd.Info.ID)] = rnd + s.list[newRoundIdentity(rnd.Id, rnd.Source, rnd.EpdId)] = rnd } return nil } + +func storeRoundInfo(kv *versioned.KV, info *pb.RoundInfo, recipient *id.ID, + ephID ephemeral.Id) error { + now := netTime.Now() + + data, err := proto.Marshal(info) + if err != nil { + return errors.WithMessagef(err, + "Failed to store individual unchecked round") + } + + obj := versioned.Object{ + Version: roundInfoVersion, + Timestamp: now, + Data: data, + } + + return kv.Set( + roundKey(id.Round(info.ID), recipient, ephID), roundInfoVersion, &obj) +} + +func loadRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID, + ephID ephemeral.Id) (*pb.RoundInfo, error) { + + vo, err := kv.Get(roundKey(id, recipient, ephID), roundInfoVersion) + if err != nil { + return nil, err + } + + ri := &pb.RoundInfo{} + if err = proto.Unmarshal(vo.Data, ri); err != nil { + return nil, errors.WithMessagef(err, "Failed to unmarshal roundInfo") + } + + return ri, nil +} + +func deleteRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID, + ephID ephemeral.Id) error { + return kv.Delete(roundKey(id, recipient, ephID), roundInfoVersion) +} + +func roundKey(roundID id.Round, recipient *id.ID, ephID ephemeral.Id) string { + return roundKeyPrefix + newRoundIdentity(roundID, recipient, ephID).String() +} diff --git a/storage/rounds/uncheckedRounds_test.go b/storage/rounds/uncheckedRounds_test.go index 63e8c195f4f2ffa752979e5f5780acdc6d2d3f16..4afecef152219391e6e3990824cdaa047be0783e 100644 --- a/storage/rounds/uncheckedRounds_test.go +++ b/storage/rounds/uncheckedRounds_test.go @@ -24,57 +24,52 @@ func TestNewUncheckedStore(t *testing.T) { kv := versioned.NewKV(make(ekv.Memstore)) testStore := &UncheckedRoundStore{ - list: make(map[id.Round]UncheckedRound), + list: make(map[roundIdentity]UncheckedRound), kv: kv.Prefix(uncheckedRoundPrefix), } store, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not create unchecked stor: %v", err) + t.Fatalf("NewUncheckedStore returned an error: %+v", err) } // Compare manually created object with NewUnknownRoundsStore if !reflect.DeepEqual(testStore, store) { - t.Fatalf("NewUncheckedStore error: "+ - "Returned incorrect Store."+ - "\n\texpected: %+v\n\treceived: %+v", testStore, store) + t.Fatalf("NewUncheckedStore returned incorrect Store."+ + "\nexpected: %+v\nreceived: %+v", testStore, store) } rid := id.Round(1) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} + recipient := id.NewIdFromString("recipientID", id.User, t) + ephID, _, _, _ := ephemeral.GetId(recipient, id.ArrIDLen, netTime.Now().UnixNano()) uncheckedRound := UncheckedRound{ Info: roundInfo, LastCheck: netTime.Now(), NumChecks: 0, + Identity: Identity{Source: recipient, EpdId: ephID}, } - store.list[rid] = uncheckedRound + ri := newRoundIdentity(rid, recipient, ephID) + store.list[ri] = uncheckedRound if err = store.save(); err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not save store: %v", err) + t.Fatalf("Could not save store: %+v", err) } // Test if round list data matches expectedRoundData, err := store.marshal() if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not marshal data: %v", err) + t.Fatalf("Failed to marshal UncheckedRoundStore: %+v", err) } roundData, err := store.kv.Get(uncheckedRoundKey, uncheckedRoundVersion) if err != nil { - t.Fatalf("NewUncheckedStore error: "+ - "Could not retrieve round list form storage: %v", err) + t.Fatalf("Failed to get round list from storage: %+v", err) } if !bytes.Equal(expectedRoundData, roundData.Data) { - t.Fatalf("NewUncheckedStore error: "+ - "Data from store was not expected"+ - "\n\tExpected %v\n\tReceived: %v", expectedRoundData, roundData.Data) + t.Fatalf("Data from store unexpected.\nexpected %+v\nreceived: %v", + expectedRoundData, roundData.Data) } - } // Unit test @@ -83,51 +78,39 @@ func TestLoadUncheckedStore(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } - + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } // Load store loadedStore, err := LoadUncheckedStore(kv) if err != nil { - t.Fatalf("LoadUncheckedStore error: "+ - "Could not call LoadUncheckedStore: %v", err) + t.Fatalf("LoadUncheckedStore returned an error: %+v", err) } // Check if round is in loaded store - rnd, exists := loadedStore.list[rid] + ri := newRoundIdentity(rid, source, ephId) + rnd, exists := loadedStore.list[ri] if !exists { - t.Fatalf("LoadUncheckedStore error: "+ - "Added round %d not found in loaded store", rid) + t.Fatalf("Added round %d not found in loaded store.", rid) } // Check if set values are expected - if !bytes.Equal(rnd.EpdId[:], ephId[:]) || - !source.Cmp(rnd.Source) { - t.Fatalf("LoadUncheckedStore error: "+ - "Values in loaded round %d are not expected."+ - "\n\tExpected ephemeral: %v"+ - "\n\tReceived ephemeral: %v"+ - "\n\tExpected source: %v"+ - "\n\tReceived source: %v", rid, - ephId, rnd.EpdId, - source, rnd.Source) + if !bytes.Equal(rnd.EpdId[:], ephId[:]) || !source.Cmp(rnd.Source) { + t.Fatalf("Values in loaded round %d are not expected."+ + "\nexpected ephemeral: %d\nreceived ephemeral: %d"+ + "\nexpected source: %s\nreceived source: %s", + rid, ephId.Int64(), rnd.EpdId.Int64(), source, rnd.Source) } - } // Unit test @@ -136,28 +119,23 @@ func TestUncheckedRoundStore_AddRound(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("AddRound error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("AddRound error: "+ - "Could not add round to store: %v", err) + t.Fatalf("AddRound returned an error: %+v", err) } - if _, exists := testStore.list[rid]; !exists { - t.Errorf("AddRound error: " + - "Could not find added round in list") + ri := newRoundIdentity(rid, source, ephId) + if _, exists := testStore.list[ri]; !exists { + t.Error("Could not find added round in list") } - } // Unit test @@ -166,50 +144,118 @@ func TestUncheckedRoundStore_GetRound(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("GetRound error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add round to store rid := id.Round(0) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} - source := id.NewIdFromBytes([]byte("Sauron"), t) - err = testStore.AddRound(roundInfo, ephId, source) + source := id.NewIdFromString("Sauron", id.User, t) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Fatalf("GetRound error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } // Retrieve round that was inserted - retrievedRound, exists := testStore.GetRound(rid) + retrievedRound, exists := testStore.GetRound(rid, source, ephId) if !exists { t.Fatalf("GetRound error: " + "Could not get round from store") } - if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) || - !source.Cmp(retrievedRound.Source) { - t.Fatalf("GetRound error: "+ - "Values in loaded round %d are not expected."+ - "\n\tExpected ephemeral: %v"+ - "\n\tReceived ephemeral: %v"+ - "\n\tExpected source: %v"+ - "\n\tReceived source: %v", rid, - ephId, retrievedRound.EpdId, - source, retrievedRound.Source) + if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId.Int64(), + retrievedRound.EpdId.Int64()) + } + + if !source.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source, retrievedRound.Source) } // Try to pull unknown round from store unknownRound := id.Round(1) - _, exists = testStore.GetRound(unknownRound) + unknownRecipient := id.NewIdFromString("invalidID", id.User, t) + unknownEphId := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18} + _, exists = testStore.GetRound(unknownRound, unknownRecipient, unknownEphId) if exists { - t.Fatalf("GetRound error: " + - "Should not find unknown round in store.") + t.Fatalf("Should not find unknown round %d in store.", unknownRound) + } +} + +// Tests that two identifies for the same round can be retrieved separately. +func TestUncheckedRoundStore_GetRound_TwoIDs(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + + s, err := NewUncheckedStore(kv) + if err != nil { + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) + } + + // Add round to store for the same round but two sources + rid := id.Round(0) + roundInfo := &pb.RoundInfo{ID: uint64(rid)} + ephId1 := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source1 := id.NewIdFromString("Sauron", id.User, t) + err = s.AddRound(rid, roundInfo, source1, ephId1) + if err != nil { + t.Fatalf("Failed to add round for source 1 to store: %+v", err) + } + + ephId2 := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18} + source2 := id.NewIdFromString("Sauron2", id.User, t) + err = s.AddRound(rid, roundInfo, source2, ephId2) + if err != nil { + t.Fatalf("Failed to add round for source 2 to store: %+v", err) + } + + // Increment each a set number of times + incNum1, incNum2 := 3, 13 + for i := 0; i < incNum1; i++ { + if err = s.IncrementCheck(rid, source1, ephId1); err != nil { + t.Errorf("Failed to incremement for source 1 (%d): %+v", i, err) + } + } + for i := 0; i < incNum2; i++ { + if err = s.IncrementCheck(rid, source2, ephId2); err != nil { + t.Errorf("Failed to incremement for source 2 (%d): %+v", i, err) + } + } + + // Retrieve round that was inserted + retrievedRound, exists := s.GetRound(rid, source1, ephId1) + if !exists { + t.Fatalf("Could not get round for source 1 from store") + } + + if !bytes.Equal(retrievedRound.EpdId[:], ephId1[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId1.Int64(), + retrievedRound.EpdId.Int64()) + } + + if !source1.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source1, retrievedRound.Source) + } + + retrievedRound, exists = s.GetRound(rid, source2, ephId2) + if !exists { + t.Fatalf("Could not get round for source 2 from store") + } + + if !bytes.Equal(retrievedRound.EpdId[:], ephId2[:]) { + t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+ + "\nexpected: %d\nreceived: %d", rid, ephId2.Int64(), + retrievedRound.EpdId.Int64()) } + if !source2.Cmp(retrievedRound.Source) { + t.Fatalf("Retrieved source ID for round %d does not match expected."+ + "\nexpected: %s\nreceived: %s", rid, source2, retrievedRound.Source) + } } // Unit test @@ -218,39 +264,36 @@ func TestUncheckedRoundStore_GetList(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("GetList error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(rid)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("GetList error: "+ - "Could not add round to store: %v", err) + t.Errorf("Failed to add round to store: %+v", err) } } // Retrieve list - retrievedList := testStore.GetList() + retrievedList := testStore.GetList(t) if len(retrievedList) != numRounds { - t.Errorf("GetList error: "+ - "List returned is not of expected size."+ - "\n\tExpected: %v\n\tReceived: %v", numRounds, len(retrievedList)) + t.Errorf("List returned is not of expected size."+ + "\nexpected: %d\nreceived: %d", numRounds, len(retrievedList)) } for i := 0; i < numRounds; i++ { rid := id.Round(i) - if _, exists := retrievedList[rid]; !exists { - t.Errorf("GetList error: "+ - "Retrieved list does not contain expected round %d.", rid) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(i), id.User, t) + ri := newRoundIdentity(rid, source, ephId) + if _, exists := retrievedList[ri]; !exists { + t.Errorf("Retrieved list does not contain expected round %d.", rid) } } @@ -262,62 +305,55 @@ func TestUncheckedRoundStore_IncrementCheck(t *testing.T) { testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("IncrementCheck error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { - rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(i)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } } testRound := id.Round(3) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(testRound), id.User, t) numChecks := 4 for i := 0; i < numChecks; i++ { - err = testStore.IncrementCheck(testRound) + err = testStore.IncrementCheck(testRound, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not increment check for round %d: %v", testRound, err) + t.Errorf("Could not increment check for round %d: %v", testRound, err) } } - rnd, _ := testStore.GetRound(testRound) + rnd, _ := testStore.GetRound(testRound, source, ephId) if rnd.NumChecks != uint64(numChecks) { - t.Errorf("IncrementCheck error: "+ - "Round %d did not have expected number of checks."+ - "\n\tExpected: %v\n\tReceived: %v", testRound, numChecks, rnd.NumChecks) + t.Errorf("Round %d did not have expected number of checks."+ + "\nexpected: %v\nreceived: %v", testRound, numChecks, rnd.NumChecks) } // Error path: check unknown round can not be incremented unknownRound := id.Round(numRounds + 5) - err = testStore.IncrementCheck(unknownRound) + err = testStore.IncrementCheck(unknownRound, source, ephId) if err == nil { - t.Errorf("IncrementCheck error: "+ - "Should not find round %d which was not added to store", unknownRound) + t.Errorf("Should not find round %d which was not added to store", + unknownRound) } // Reach max checks, ensure that round is removed maxRound := id.Round(7) + source = id.NewIdFromUInt(uint64(maxRound), id.User, t) for i := 0; i < maxChecks+1; i++ { - err = testStore.IncrementCheck(maxRound) + err = testStore.IncrementCheck(maxRound, source, ephId) if err != nil { - t.Errorf("IncrementCheck error: "+ - "Could not increment check for round %d: %v", maxRound, err) + t.Errorf("Could not increment check for round %d: %v", maxRound, err) } - } - } // Unit test @@ -325,47 +361,40 @@ func TestUncheckedRoundStore_Remove(t *testing.T) { kv := versioned.NewKV(make(ekv.Memstore)) testStore, err := NewUncheckedStore(kv) if err != nil { - t.Fatalf("Remove error: "+ - "Could not call constructor NewUncheckedStore: %v", err) + t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err) } // Add rounds to store numRounds := 10 for i := 0; i < numRounds; i++ { - rid := id.Round(i) - roundInfo := &pb.RoundInfo{ - ID: uint64(rid), - } + roundInfo := &pb.RoundInfo{ID: uint64(i)} ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} source := id.NewIdFromUInt(uint64(i), id.User, t) - err = testStore.AddRound(roundInfo, ephId, source) + err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId) if err != nil { - t.Errorf("Remove error: "+ - "Could not add round to store: %v", err) + t.Fatalf("Failed to add round to store: %+v", err) } } // Remove round from storage removedRound := id.Round(1) - err = testStore.Remove(removedRound) + ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8} + source := id.NewIdFromUInt(uint64(removedRound), id.User, t) + err = testStore.Remove(removedRound, source, ephId) if err != nil { - t.Errorf("Remove error: "+ - "Could not removed round %d from storage: %v", removedRound, err) + t.Errorf("Could not removed round %d from storage: %v", removedRound, err) } // Check that round was removed - _, exists := testStore.GetRound(removedRound) + _, exists := testStore.GetRound(removedRound, source, ephId) if exists { - t.Errorf("Remove error: "+ - "Round %d expected to be removed from storage", removedRound) + t.Errorf("Round %d expected to be removed from storage", removedRound) } // Error path: attempt to remove unknown round unknownRound := id.Round(numRounds + 5) - err = testStore.Remove(unknownRound) + err = testStore.Remove(unknownRound, source, ephId) if err == nil { - t.Errorf("Remove error: "+ - "Should not removed round %d which is not in storage", unknownRound) + t.Errorf("Should not removed round %d which is not in storage", unknownRound) } - } diff --git a/storage/rounds/unknownRounds.go b/storage/rounds/unknownRounds.go index 3711b8787f530633a360bb91ab7911d9f8cc6830..98e863d2b911c5ad7cd7cd9d1eed68ba9e4edc2b 100644 --- a/storage/rounds/unknownRounds.go +++ b/storage/rounds/unknownRounds.go @@ -113,8 +113,9 @@ func LoadUnknownRounds(kv *versioned.KV, // in params, it removes from the map // Afterwards it adds the roundToAdd to the map if an entry isn't present // Finally it saves the modified map to disk. +// The abandon function can be used to pass the abandoned round somewhere else func (urs *UnknownRounds) Iterate(checker func(rid id.Round) bool, - roundsToAdd []id.Round) []id.Round { + roundsToAdd []id.Round, abandon func(round id.Round)) []id.Round { returnSlice := make([]id.Round, 0) urs.mux.Lock() defer urs.mux.Unlock() @@ -132,6 +133,8 @@ func (urs *UnknownRounds) Iterate(checker func(rid id.Round) bool, // If the round has been checked the maximum amount, // the rond is removed from the map if totalChecks > urs.params.MaxChecks { + localRnd := rnd + go abandon(localRnd) delete(urs.rounds, rnd) } } @@ -197,3 +200,14 @@ func (urs *UnknownRounds) Delete() { func (urs *UnknownRounds) unmarshal(b []byte) error { return json.Unmarshal(b, &urs.rounds) } + +func (urs *UnknownRounds) Get(round id.Round) (present bool, numchecked uint64) { + urs.mux.Lock() + defer urs.mux.Unlock() + numcheck, exist := urs.rounds[round] + if !exist { + return false, 0 + } + return exist, *numcheck + +} diff --git a/storage/rounds/unknownRounds_test.go b/storage/rounds/unknownRounds_test.go index f437e0839f7d37208f2d7b5c826e6a6cfeb7d5b2..7611579bf84dc6cb1e23c67f8b0c7e68ea765c2e 100644 --- a/storage/rounds/unknownRounds_test.go +++ b/storage/rounds/unknownRounds_test.go @@ -86,7 +86,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) { } // Iterate over initial map - received := store.Iterate(mockChecker, nil) + received := store.Iterate(mockChecker, nil, func(round id.Round) { return }) // Check the received list for 2 conditions: // a) that returned rounds are no longer in the map @@ -106,7 +106,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) { } // Add even round list to map - received = store.Iterate(mockChecker, roundListEven) + received = store.Iterate(mockChecker, roundListEven, func(round id.Round) { return }) if len(received) != 0 { t.Errorf("Second iteration should return an empty list (no even rounds are left)."+ @@ -116,7 +116,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) { // Iterate over map until all rounds have checks incremented over // maxCheck for i := 0; i < defaultMaxCheck+1; i++ { - _ = store.Iterate(mockChecker, []id.Round{}) + _ = store.Iterate(mockChecker, []id.Round{}, func(round id.Round) { return }) } @@ -172,7 +172,7 @@ func TestLoadUnknownRoundsStore(t *testing.T) { // Check that LoadStore works after iterate call (which implicitly saves) mockChecker := func(round id.Round) bool { return false } - received := store.Iterate(mockChecker, nil) + received := store.Iterate(mockChecker, nil, func(round id.Round) { return }) // Iterate is being called as a dummy, should not return anything if len(received) != 0 { diff --git a/storage/utility/e2eMessageBuffer.go b/storage/utility/e2eMessageBuffer.go index 259c6407a2c08c45b4f2e486182964031669b98c..5e4abcb3aa168332e5603899bf73e1e0facc8cdc 100644 --- a/storage/utility/e2eMessageBuffer.go +++ b/storage/utility/e2eMessageBuffer.go @@ -155,12 +155,12 @@ func (emb *E2eMessageBuffer) Next() (message.Send, params.E2E, bool) { message.Type(msg.MessageType)}, msg.Params, true } -func (emb *E2eMessageBuffer) Succeeded(m message.Send) { +func (emb *E2eMessageBuffer) Succeeded(m message.Send, p params.E2E) { emb.mb.Succeeded(e2eMessage{m.Recipient.Marshal(), - m.Payload, uint32(m.MessageType), params.E2E{}}) + m.Payload, uint32(m.MessageType), p}) } -func (emb *E2eMessageBuffer) Failed(m message.Send) { +func (emb *E2eMessageBuffer) Failed(m message.Send, p params.E2E) { emb.mb.Failed(e2eMessage{m.Recipient.Marshal(), - m.Payload, uint32(m.MessageType), params.E2E{}}) + m.Payload, uint32(m.MessageType), p}) } diff --git a/storage/utility/e2eMessageBuffer_test.go b/storage/utility/e2eMessageBuffer_test.go index 0aca79126fe53903957780f298e26ae1434597db..6eda44ba0cdca36ed43e9332739b968e92ed6ea9 100644 --- a/storage/utility/e2eMessageBuffer_test.go +++ b/storage/utility/e2eMessageBuffer_test.go @@ -9,6 +9,7 @@ package utility import ( "encoding/json" + "fmt" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/storage/versioned" @@ -112,7 +113,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) { if !exists { t.Error("Next() did not find any messages in buffer.") } - cmb.Succeeded(msg) + cmb.Succeeded(msg, params.E2E{}) if len(cmb.mb.messages) != 1 { t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d", @@ -127,7 +128,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) { t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d", 0, len(cmb.mb.messages)) } - cmb.Failed(msg) + cmb.Failed(msg, params.E2E{}) if len(cmb.mb.messages) != 1 { t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d", @@ -138,7 +139,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) { if !exists { t.Error("Next() did not find any messages in buffer.") } - cmb.Succeeded(msg) + cmb.Succeeded(msg, params.E2E{}) msg, _, exists = cmb.Next() if exists { @@ -174,3 +175,45 @@ func makeTestE2EMessages(n int, t *testing.T) ([]e2eMessage, []message.Send) { return msgs, send } + +func TestE2EParamMarshalUnmarshal(t *testing.T) { + msg := &e2eMessage{ + Recipient: id.DummyUser[:], + Payload: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, + MessageType: 42, + Params: params.E2E{ + Type: 1, + RetryCount: 7, + CMIX: params.CMIX{ + RoundTries: 6, + Timeout: 99, + RetryDelay: -4, + }, + }, + } + + fmt.Printf("msg1: %#v\n", msg) + + b, err := json.Marshal(&msg) + + if err != nil { + t.Errorf("Failed to Marshal E2eMessage") + } + + fmt.Printf("json: %s\n", string(b)) + + msg2 := &e2eMessage{} + + err = json.Unmarshal(b, &msg2) + + if err != nil { + t.Errorf("Failed to Unmarshal E2eMessage") + } + + fmt.Printf("msg2: %#v\n", msg2) + + if !reflect.DeepEqual(msg, msg2) { + t.Errorf("Unmarshaled message is not the same") + } + +} diff --git a/storage/utility/messageBuffer.go b/storage/utility/messageBuffer.go index 31af9f7f2a9b472873d476a686321a33145b2b42..4dd68dc2e7e3f45b4e5315e1aebc5b23b0eb1d04 100644 --- a/storage/utility/messageBuffer.go +++ b/storage/utility/messageBuffer.go @@ -21,6 +21,10 @@ import ( // message stored in the buffer. type MessageHash [16]byte +func (m MessageHash) String() string { + return base64.StdEncoding.EncodeToString(m[:]) +} + // Sub key used in building keys for saving the message to the key value store const messageSubKey = "bufferedMessage" @@ -248,23 +252,31 @@ func (mb *MessageBuffer) Next() (interface{}, bool) { return format.Message{}, false } - // Pop the next MessageHash from the "not processing" list - h := next(mb.messages) - jww.TRACE.Printf("Critical Messages Next returned %s", - base64.StdEncoding.EncodeToString(h[:])) + var m interface{} + var err error - delete(mb.messages, h) + //run until empty or a valid message is + for m == nil && len(mb.messages) > 0 { + // Pop the next MessageHash from the "not processing" list + h := next(mb.messages) + jww.TRACE.Printf("Critical Messages Next returned %s", + base64.StdEncoding.EncodeToString(h[:])) - // Add message to list of processing messages - mb.processingMessages[h] = struct{}{} + delete(mb.messages, h) - // Retrieve the message for storage - m, err := mb.handler.LoadMessage(mb.kv, makeStoredMessageKey(mb.key, h)) - if err != nil { - jww.FATAL.Panicf("Could not load message: %v", err) + // Add message to list of processing messages + mb.processingMessages[h] = struct{}{} + + // Retrieve the message for storage + m, err = mb.handler.LoadMessage(mb.kv, makeStoredMessageKey(mb.key, h)) + if err != nil { + jww.ERROR.Printf("Failed to load message %s from store, "+ + "this may happen on occasion due to replays to increase "+ + "reliability: %v", h, err) + } } - return m, true + return m, m != nil } // next returns the first MessageHash in the map returned by range. @@ -288,17 +300,20 @@ func (mb *MessageBuffer) Succeeded(m interface{}) { delete(mb.processingMessages, h) delete(mb.messages, h) - // Done message from key value store - err := mb.handler.DeleteMessage(mb.kv, makeStoredMessageKey(mb.key, h)) + // Save modified buffer to key value store + err := mb.save() if err != nil { jww.FATAL.Fatalf("Failed to save: %v", err) } - // Save modified buffer to key value store - err = mb.save() + // Done message from key value store + err = mb.handler.DeleteMessage(mb.kv, makeStoredMessageKey(mb.key, h)) if err != nil { - jww.FATAL.Fatalf("Failed to save: %v", err) + jww.ERROR.Printf("Failed to delete message from store, "+ + "this may happen on occasion due to replays to increase "+ + "reliability: %v", err) } + } // Failed sets a message as failed to process. It changes the message back to @@ -314,6 +329,12 @@ func (mb *MessageBuffer) Failed(m interface{}) { // Done from "processing" state delete(mb.processingMessages, h) + // Save message as versioned object + err := mb.handler.SaveMessage(mb.kv, m, makeStoredMessageKey(mb.key, h)) + if err != nil { + jww.FATAL.Panicf("Error saving message: %v", err) + } + // Add to "not processed" state mb.messages[h] = struct{}{} } diff --git a/storage/utility/messageBuffer_test.go b/storage/utility/messageBuffer_test.go index 5e30960f9ccc3ad7060a876b7924fd3f0d8f3016..6cb9f3660ba7c1f7433d9beb94edfd9a9515c674 100644 --- a/storage/utility/messageBuffer_test.go +++ b/storage/utility/messageBuffer_test.go @@ -244,6 +244,25 @@ func TestMessageBuffer_Next(t *testing.T) { } } +func TestMessageBuffer_InvalidNext(t *testing.T) { + // Create new MessageBuffer and fill with messages + testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), newTestHandler(), "testKey") + if err != nil { + t.Fatalf("Failed to create new MessageBuffer: %v", err) + } + m := []byte("This is a message that should fail") + h := testMB.handler.HashMessage(m) + testMB.Add(m) + err = testMB.handler.DeleteMessage(testMB.kv, makeStoredMessageKey(testMB.key, h)) + if err != nil { + t.Fatalf("Failed to set up test (delete from kv failed): %+v", err) + } + msg, exists := testMB.Next() + if msg != nil || exists { + t.Fatalf("This should fail with an invalid message, instead got: %+v, %+v", m, exists) + } +} + // Tests happy path of MessageBuffer.Remove(). func TestMessageBuffer_Succeeded(t *testing.T) { th := newTestHandler() diff --git a/ud/addFact.go b/ud/addFact.go index a32668f7179d1282f7f96438f13f76905e8b2b8f..1fc1a15f0a24fd327445d4d8220517863cd5eda6 100644 --- a/ud/addFact.go +++ b/ud/addFact.go @@ -17,9 +17,9 @@ type addFactComms interface { SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) } -// Adds a fact for the user to user discovery. Will only succeed if the -// user is already registered and the system does not have the fact currently -// registered for any user. +// SendRegisterFact adds a fact for the user to user discovery. Will only +// succeed if the user is already registered and the system does not have the +// fact currently registered for any user. // This does not complete the fact registration process, it returns a // confirmation id instead. Over the communications system the fact is // associated with, a code will be sent. This confirmation ID needs to be @@ -43,10 +43,10 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin } // Create a hash of our fact - fhash := factID.Fingerprint(f) + fHash := factID.Fingerprint(f) // Sign our inFact for putting into the request - fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil) + fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) if err != nil { return "", err } @@ -58,11 +58,17 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin Fact: inFact.Fact, FactType: uint32(inFact.T), }, - FactSig: fsig, + FactSig: fSig, + } + + // Get UD host + host, err := m.getHost() + if err != nil { + return "", err } // Send the message - response, err := aFC.SendRegisterFact(m.host, &remFactMsg) + response, err := aFC.SendRegisterFact(host, &remFactMsg) confirmationID := "" if response != nil { diff --git a/ud/addFact_test.go b/ud/addFact_test.go index 3e1ce5ad31464a042d70a1de251d8b8668a3e8fa..eebb92a1d867c3873897ad56eac60d1b415ec5f2 100644 --- a/ud/addFact_test.go +++ b/ud/addFact_test.go @@ -1,6 +1,7 @@ package ud import ( + "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" @@ -12,20 +13,16 @@ import ( type testAFC struct{} -// Dummy implementation of SendRegisterFact so we don't need -// to run our own UDB server -func (rFC *testAFC) SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) { +// Dummy implementation of SendRegisterFact so that we don't need to run our own +// UDB server. +func (rFC *testAFC) SendRegisterFact(*connect.Host, *pb.FactRegisterRequest) ( + *pb.FactRegisterResponse, error) { return &pb.FactRegisterResponse{}, nil } // Test that the addFact function completes successfully func TestAddFact(t *testing.T) { isReg := uint32(1) - // Add our host, addFact uses it to get the ID of the user - h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams()) - if err != nil { - t.Fatal(err) - } // Create a new Private Key to use for signing the Fact rng := csprng.NewSystemRNG() @@ -34,9 +31,15 @@ func TestAddFact(t *testing.T) { t.Fatal(err) } + comms, err := client.NewClientComms(nil, nil, nil, nil) + if err != nil { + t.Errorf("Failed to start client comms: %+v", err) + } + // Create our Manager object m := Manager{ - host: h, + comms: comms, + net: newTestNetworkManager(t), privKey: cpk, registered: &isReg, } @@ -49,13 +52,13 @@ func TestAddFact(t *testing.T) { T: 2, } - // Setup a dummy comms that implements SendRegisterFact + // Set up a dummy comms that implements SendRegisterFact // This way we don't need to run UDB just to check that this // function works. - tafc := testAFC{} + tAFC := testAFC{} uid := &id.ID{} // Run addFact and see if it returns without an error! - _, err = m.addFact(f, uid, &tafc) + _, err = m.addFact(f, uid, &tAFC) if err != nil { t.Fatal(err) } diff --git a/ud/confirmFact.go b/ud/confirmFact.go index a625e7e6337d8facc4cff7096d078ee53b264a7d..b7c4294e2c026e35da1ae70b7739fe6feea0c0bd 100644 --- a/ud/confirmFact.go +++ b/ud/confirmFact.go @@ -12,8 +12,9 @@ type confirmFactComm interface { SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) } -// Confirms a fact first registered via AddFact. The confirmation ID comes from -// AddFact while the code will come over the associated communications system +// SendConfirmFact confirms a fact first registered via AddFact. The +// confirmation ID comes from AddFact while the code will come over the +// associated communications system. func (m *Manager) SendConfirmFact(confirmationID, code string) error { jww.INFO.Printf("ud.SendConfirmFact(%s, %s)", confirmationID, code) if err := m.confirmFact(confirmationID, code, m.comms); err != nil { @@ -28,10 +29,16 @@ func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm) "client is not registered") } + // Get UD host + host, err := m.getHost() + if err != nil { + return err + } + msg := &pb.FactConfirmRequest{ ConfirmationID: confirmationID, Code: code, } - _, err := comm.SendConfirmFact(m.host, msg) + _, err = comm.SendConfirmFact(host, msg) return err } diff --git a/ud/confirmFact_test.go b/ud/confirmFact_test.go index 2e060c09857b6e0f90d9fac6b5a81f5757d4d79b..9fe29b0a4491e6df330595d27d570e98d87f7fb9 100644 --- a/ud/confirmFact_test.go +++ b/ud/confirmFact_test.go @@ -1,10 +1,10 @@ package ud import ( + "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" - "gitlab.com/xx_network/primitives/id" "reflect" "testing" ) @@ -20,17 +20,17 @@ func (t *testComm) SendConfirmFact(_ *connect.Host, message *pb.FactConfirmReque // Happy path. func TestManager_confirmFact(t *testing.T) { - // Create new host - host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams()) + isReg := uint32(1) + + comms, err := client.NewClientComms(nil, nil, nil, nil) if err != nil { - t.Fatalf("Could not create a new host: %+v", err) + t.Errorf("Failed to start client comms: %+v", err) } - isReg := uint32(1) - // Set up manager m := &Manager{ - host: host, + comms: comms, + net: newTestNetworkManager(t), registered: &isReg, } diff --git a/ud/lookup.go b/ud/lookup.go index e7bdb0da0c0ed10bc8ccaa15c79bc6b9c30b8a9e..c5d8b4dd96757ec69b52e629ad69c131b92ed7aa 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -1,11 +1,11 @@ package ud import ( - "fmt" "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" "time" ) @@ -38,7 +38,13 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat m.lookupResponseProcess(uid, callback, payload, err) } - err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, LookupTag, + // Get UD contact + c, err := m.getContact() + if err != nil { + return err + } + + err = m.single.TransmitSingleUse(c, requestMarshaled, LookupTag, maxLookupMessages, f, timeout) if err != nil { return errors.WithMessage(err, "Failed to transmit lookup request.") @@ -67,11 +73,17 @@ func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback, return } - fmt.Printf("pubKey: %+v\n", lookupResponse.PubKey) c := contact.Contact{ ID: uid, DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey), } + if lookupResponse.Username != "" { + c.Facts = fact.FactList{{ + Fact: lookupResponse.Username, + T: fact.Username, + }} + } + go callback(c, nil) } diff --git a/ud/lookup_test.go b/ud/lookup_test.go index 02162faaf2e47e555fe54dd8a6fd1d9a3dd36f03..63cb7ab60c07c3b5740ab676a3579667192025c3 100644 --- a/ud/lookup_test.go +++ b/ud/lookup_test.go @@ -5,6 +5,8 @@ import ( "github.com/pkg/errors" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/xx_network/crypto/large" @@ -20,9 +22,17 @@ import ( func TestManager_Lookup(t *testing.T) { // Set up manager isReg := uint32(1) + + comms, err := client.NewClientComms(nil, nil, nil, nil) + if err != nil { + t.Errorf("Failed to start client comms: %+v", err) + } + m := &Manager{ + comms: comms, + storage: storage.InitTestingSession(t), + net: newTestNetworkManager(t), grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - udContact: contact.Contact{ID: &id.UDB}, single: &mockSingleLookup{}, registered: &isReg, } @@ -41,7 +51,7 @@ func TestManager_Lookup(t *testing.T) { uid := id.NewIdFromUInt(0x500000000000000, id.User, t) // Run the lookup - err := m.Lookup(uid, callback, 10*time.Millisecond) + err = m.Lookup(uid, callback, 10*time.Millisecond) if err != nil { t.Errorf("Lookup() returned an error: %+v", err) } diff --git a/ud/manager.go b/ud/manager.go index 9342419bdbbe5b8edb01bb32f858032cc6da8186..1b9de113a558afc451e2acabfb5dabde096a4f88 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -35,59 +35,43 @@ type Manager struct { net interfaces.NetworkManager // Loaded from external access - udContact contact.Contact - privKey *rsa.PrivateKey - grp *cyclic.Group + privKey *rsa.PrivateKey + grp *cyclic.Group // internal structures - host *connect.Host single SingleInterface myID *id.ID registered *uint32 } -// New manager builds a new user discovery manager. It requires that an -// updated NDF is available and will error if one is not. +// NewManager builds a new user discovery manager. It requires that an updated +// NDF is available and will error if one is not. func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { jww.INFO.Println("ud.NewManager()") if client.NetworkFollowerStatus() != api.Running { - return nil, errors.New("cannot start UD Manager when network follower is not " + - "running.") + return nil, errors.New( + "cannot start UD Manager when network follower is not running.") } m := &Manager{ - client: client, - comms: client.GetComms(), - rng: client.GetRng(), - sw: client.GetSwitchboard(), - storage: client.GetStorage(), - net: client.GetNetworkInterface(), - udContact: contact.Contact{}, - single: single, + client: client, + comms: client.GetComms(), + rng: client.GetRng(), + sw: client.GetSwitchboard(), + storage: client.GetStorage(), + net: client.GetNetworkInterface(), + single: single, } - var err error - - // check that user discovery is available in the ndf + // check that user discovery is available in the NDF def := m.net.GetInstance().GetPartialNdf().Get() - if m.udContact.ID, err = id.Unmarshal(def.UDB.ID); err != nil { - return nil, errors.WithMessage(err, "NDF does not have User Discovery "+ - "information; is there network access?: ID could not be "+ - "unmarshaled.") - } if def.UDB.Cert == "" { return nil, errors.New("NDF does not have User Discovery information, " + "is there network access?: Cert not present.") } - // Unmarshal UD DH public key - m.udContact.DhPubKey = m.storage.E2e().GetGroup().NewInt(1) - if err = m.udContact.DhPubKey.UnmarshalJSON(def.UDB.DhPubKey); err != nil { - return nil, errors.WithMessage(err, "Failed to unmarshal UD DH public key.") - } - // Create the user discovery host object hp := connect.GetDefaultHostParams() // Client will not send KeepAlive packets @@ -95,11 +79,6 @@ func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { hp.MaxRetries = 3 hp.SendTimeout = 3 * time.Second hp.AuthEnabled = false - m.host, err = m.comms.AddHost(&id.UDB, def.UDB.Address, []byte(def.UDB.Cert), hp) - if err != nil { - return nil, errors.WithMessage(err, "User Discovery host object could "+ - "not be constructed.") - } m.myID = m.storage.User().GetCryptographicIdentity().GetReceptionID() @@ -114,3 +93,60 @@ func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { return m, nil } + +// getHost returns the current UD host for the UD ID found in the NDF. If the +// host does not exist, then it is added and returned +func (m *Manager) getHost() (*connect.Host, error) { + netDef := m.net.GetInstance().GetPartialNdf().Get() + + // Unmarshal UD ID from the NDF + udID, err := id.Unmarshal(netDef.UDB.ID) + if err != nil { + return nil, errors.Errorf("failed to unmarshal UD ID from NDF: %+v", err) + } + + // Return the host, if it exists + host, exists := m.comms.GetHost(udID) + if exists { + return host, nil + } + + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + + // Add a new host and return it if it does not already exist + host, err = m.comms.AddHost(udID, netDef.UDB.Address, + []byte(netDef.UDB.Cert), params) + if err != nil { + return nil, errors.WithMessage(err, "User Discovery host object could "+ + "not be constructed.") + } + + return host, nil +} + +// getContact returns the contact for UD as retrieved from the NDF. +func (m *Manager) getContact() (contact.Contact, error) { + netDef := m.net.GetInstance().GetPartialNdf().Get() + + // Unmarshal UD ID from the NDF + udID, err := id.Unmarshal(netDef.UDB.ID) + if err != nil { + return contact.Contact{}, + errors.Errorf("failed to unmarshal UD ID from NDF: %+v", err) + } + + // Unmarshal UD DH public key + dhPubKey := m.storage.E2e().GetGroup().NewInt(1) + if err = dhPubKey.UnmarshalJSON(netDef.UDB.DhPubKey); err != nil { + return contact.Contact{}, + errors.WithMessage(err, "Failed to unmarshal UD DH public key.") + } + + return contact.Contact{ + ID: udID, + DhPubKey: dhPubKey, + OwnershipProof: nil, + Facts: nil, + }, nil +} diff --git a/ud/register.go b/ud/register.go index 8a25bafc01d150708136945a119de470b6e64e82..f6b205a069fa054708f75c591c147a7660f8cfb3 100644 --- a/ud/register.go +++ b/ud/register.go @@ -81,8 +81,14 @@ func (m *Manager) register(username string, comm registerUserComms) error { FactSig: signedFact, } + // Get UD host + host, err := m.getHost() + if err != nil { + return err + } + // Register user with user discovery - _, err = comm.SendRegisterUser(m.host, msg) + _, err = comm.SendRegisterUser(host, msg) if err == nil { err = m.setRegistered() diff --git a/ud/register_test.go b/ud/register_test.go index 7cfd66f1da801bf54b3c853b2232eb3d2f0d6645..0bdd3ef54f664235549b98e40f380f7bc21585c3 100644 --- a/ud/register_test.go +++ b/ud/register_test.go @@ -3,6 +3,7 @@ package ud import ( "bytes" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/factID" "gitlab.com/elixxir/crypto/fastRNG" @@ -12,7 +13,6 @@ import ( "gitlab.com/xx_network/comms/messages" "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/id" "reflect" "testing" ) @@ -28,17 +28,17 @@ func (t *testRegisterComm) SendRegisterUser(_ *connect.Host, msg *pb.UDBUserRegi // Happy path. func TestManager_register(t *testing.T) { - // Create new host - host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams()) + isReg := uint32(0) + + comms, err := client.NewClientComms(nil, nil, nil, nil) if err != nil { - t.Fatalf("Could not create a new host: %+v", err) + t.Errorf("Failed to start client comms: %+v", err) } - isReg := uint32(0) - // Set up manager m := &Manager{ - host: host, + comms: comms, + net: newTestNetworkManager(t), rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), storage: storage.InitTestingSession(t), registered: &isReg, diff --git a/ud/remove.go b/ud/remove.go index 99d9447f330cf0a5c133975abea3a29de1a71b0e..67f6721773baed94402963fad95726dbe34e9365 100644 --- a/ud/remove.go +++ b/ud/remove.go @@ -17,7 +17,7 @@ type removeFactComms interface { SendRemoveFact(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error) } -// Removes a previously confirmed fact. Will fail if the fact is not +// RemoveFact removes a previously confirmed fact. Will fail if the fact is not // associated with this client. func (m *Manager) RemoveFact(fact fact.Fact) error { jww.INFO.Printf("ud.RemoveFact(%s)", fact.Stringify()) @@ -38,10 +38,10 @@ func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error { } // Create a hash of our fact - fhash := factID.Fingerprint(fact) + fHash := factID.Fingerprint(fact) // Sign our inFact for putting into the request - fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil) + fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) if err != nil { return err } @@ -50,11 +50,17 @@ func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error { remFactMsg := mixmessages.FactRemovalRequest{ UID: m.myID.Marshal(), RemovalData: &mmFact, - FactSig: fsig, + FactSig: fSig, + } + + // Get UD host + host, err := m.getHost() + if err != nil { + return err } // Send the message - _, err = rFC.SendRemoveFact(m.host, &remFactMsg) + _, err = rFC.SendRemoveFact(host, &remFactMsg) // Return the error return err @@ -64,7 +70,7 @@ type removeUserComms interface { SendRemoveUser(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error) } -// Removes a previously confirmed fact. Will fail if the fact is not +// RemoveUser removes a previously confirmed fact. Will fail if the fact is not // associated with this client. func (m *Manager) RemoveUser(fact fact.Fact) error { jww.INFO.Printf("ud.RemoveUser(%s)", fact.Stringify()) @@ -85,10 +91,10 @@ func (m *Manager) removeUser(fact fact.Fact, rFC removeUserComms) error { } // Create a hash of our fact - fhash := factID.Fingerprint(fact) + fHash := factID.Fingerprint(fact) // Sign our inFact for putting into the request - fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil) + fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) if err != nil { return err } @@ -100,8 +106,14 @@ func (m *Manager) removeUser(fact fact.Fact, rFC removeUserComms) error { FactSig: fsig, } + // Get UD host + host, err := m.getHost() + if err != nil { + return err + } + // Send the message - _, err = rFC.SendRemoveUser(m.host, &remFactMsg) + _, err = rFC.SendRemoveUser(host, &remFactMsg) // Return the error return err diff --git a/ud/remove_test.go b/ud/remove_test.go index d65cb0e8ccd888e374e42c2585fe41dfe2305c66..5705a843a4a182b3ad959243f4b94092551803de 100644 --- a/ud/remove_test.go +++ b/ud/remove_test.go @@ -1,6 +1,7 @@ package ud import ( + "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" @@ -13,16 +14,12 @@ import ( type testRFC struct{} -func (rFC *testRFC) SendRemoveFact(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) { +func (rFC *testRFC) SendRemoveFact(*connect.Host, *pb.FactRemovalRequest) ( + *messages.Ack, error) { return &messages.Ack{}, nil } func TestRemoveFact(t *testing.T) { - h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams()) - if err != nil { - t.Fatal(err) - } - rng := csprng.NewSystemRNG() cpk, err := rsa.GenerateKey(rng, 2048) if err != nil { @@ -31,9 +28,15 @@ func TestRemoveFact(t *testing.T) { isReg := uint32(1) - m := Manager{ - comms: nil, - host: h, + comms, err := client.NewClientComms(nil, nil, nil, nil) + if err != nil { + t.Errorf("Failed to start client comms: %+v", err) + } + + // Set up manager + m := &Manager{ + comms: comms, + net: newTestNetworkManager(t), privKey: cpk, registered: &isReg, myID: &id.ID{}, @@ -44,23 +47,20 @@ func TestRemoveFact(t *testing.T) { T: 2, } - trfc := testRFC{} + tRFC := testRFC{} - err = m.removeFact(f, &trfc) + err = m.removeFact(f, &tRFC) if err != nil { t.Fatal(err) } } -func (rFC *testRFC) SendRemoveUser(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) { +func (rFC *testRFC) SendRemoveUser(*connect.Host, *pb.FactRemovalRequest) ( + *messages.Ack, error) { return &messages.Ack{}, nil } func TestRemoveUser(t *testing.T) { - h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams()) - if err != nil { - t.Fatal(err) - } rng := csprng.NewSystemRNG() cpk, err := rsa.GenerateKey(rng, 2048) @@ -70,9 +70,15 @@ func TestRemoveUser(t *testing.T) { isReg := uint32(1) - m := Manager{ - comms: nil, - host: h, + comms, err := client.NewClientComms(nil, nil, nil, nil) + if err != nil { + t.Errorf("Failed to start client comms: %+v", err) + } + + // Set up manager + m := &Manager{ + comms: comms, + net: newTestNetworkManager(t), privKey: cpk, registered: &isReg, myID: &id.ID{}, @@ -83,9 +89,9 @@ func TestRemoveUser(t *testing.T) { T: 2, } - trfc := testRFC{} + tRFC := testRFC{} - err = m.removeUser(f, &trfc) + err = m.removeUser(f, &tRFC) if err != nil { t.Fatal(err) } diff --git a/ud/search.go b/ud/search.go index 41001234896bba755fd6f032afdb19df8a23b22b..83f33901ca97938c4d74966f11d3909fdd227061 100644 --- a/ud/search.go +++ b/ud/search.go @@ -14,7 +14,7 @@ import ( // SearchTag specifies which callback to trigger when UD receives a search // request. -const SearchTag = "xxNetwork_UdLookup" +const SearchTag = "xxNetwork_UdSearch" // TODO: reconsider where this comes from const maxSearchMessages = 20 @@ -45,7 +45,13 @@ func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout ti m.searchResponseHandler(factMap, callback, payload, err) } - err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, SearchTag, + // Get UD contact + c, err := m.getContact() + if err != nil { + return err + } + + err = m.single.TransmitSingleUse(c, requestMarshaled, SearchTag, maxSearchMessages, f, timeout) if err != nil { return errors.WithMessage(err, "Failed to transmit search request.") @@ -86,7 +92,7 @@ func (m *Manager) searchResponseHandler(factMap map[string]fact.Fact, return } - //return an error if no facts are found + // return an error if no facts are found if len(searchResponse.Contacts) == 0 { go callback(nil, errors.New("No contacts found in search")) } diff --git a/ud/search_test.go b/ud/search_test.go index c333cf08c5beede7a0ea8389b3adcbb2b4e18544..d0a1edfb9c9dbd1623ae5e5b9e1234c421c87b44 100644 --- a/ud/search_test.go +++ b/ud/search_test.go @@ -3,9 +3,11 @@ package ud import ( "fmt" "github.com/golang/protobuf/proto" - errors "github.com/pkg/errors" + "github.com/pkg/errors" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/factID" @@ -23,10 +25,19 @@ import ( func TestManager_Search(t *testing.T) { // Set up manager isReg := uint32(1) - grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2)) + + comms, err := client.NewClientComms(nil, nil, nil, nil) + if err != nil { + t.Errorf("Failed to start client comms: %+v", err) + } + + store := storage.InitTestingSession(t) + m := &Manager{ - grp: grp, - udContact: contact.Contact{ID: &id.UDB, DhPubKey: grp.NewInt(42)}, + comms: comms, + storage: store, + net: newTestNetworkManager(t), + grp: store.E2e().GetGroup(), single: &mockSingleSearch{}, registered: &isReg, } @@ -62,7 +73,7 @@ func TestManager_Search(t *testing.T) { }) } - err := m.Search(factList, callback, 10*time.Millisecond) + err = m.Search(factList, callback, 10*time.Millisecond) if err != nil { t.Errorf("Search() returned an error: %+v", err) } @@ -74,7 +85,12 @@ func TestManager_Search(t *testing.T) { t.Errorf("Callback returned an error: %+v", cb.err) } - expectedContacts := []contact.Contact{m.udContact} + c, err := m.getContact() + if err != nil { + t.Errorf("Failed to get UD contact: %+v", err) + } + + expectedContacts := []contact.Contact{c} if !contact.Equal(expectedContacts[0], cb.c[0]) { t.Errorf("Failed to get expected Contacts."+ "\n\texpected: %+v\n\treceived: %+v", expectedContacts, cb.c) diff --git a/ud/udMessages.pb.go b/ud/udMessages.pb.go index 82477b3fdd3d3a968997295895f227e1096c7501..0aae2063325f57b73c42ff53e30020225aefcd7e 100644 --- a/ud/udMessages.pb.go +++ b/ud/udMessages.pb.go @@ -266,6 +266,7 @@ func (m *LookupSend) GetUserID() []byte { // Message sent from UDB for looking up a user type LookupResponse struct { PubKey []byte `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -304,6 +305,13 @@ func (m *LookupResponse) GetPubKey() []byte { return nil } +func (m *LookupResponse) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + func (m *LookupResponse) GetError() string { if m != nil { return m.Error @@ -325,23 +333,23 @@ func init() { } var fileDescriptor_9e0cfdc16fb09bb6 = []byte{ - // 281 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4b, 0xf4, 0x30, - 0x10, 0xc5, 0xc9, 0x6e, 0xbb, 0x5f, 0x77, 0xbe, 0xa5, 0x4a, 0x10, 0x29, 0x9e, 0x4a, 0xf4, 0x50, - 0x04, 0x0b, 0xae, 0x77, 0x0f, 0x2a, 0xa2, 0xa8, 0x97, 0xec, 0xcd, 0x5b, 0xb6, 0x1d, 0xb7, 0x22, - 0x36, 0x21, 0x93, 0x1e, 0xf6, 0xee, 0x1f, 0x2e, 0x4d, 0x63, 0x17, 0x61, 0xbd, 0xe5, 0xcd, 0xcc, - 0x8f, 0x79, 0xf3, 0x02, 0x87, 0x5d, 0xfd, 0x82, 0x44, 0x6a, 0x83, 0x54, 0x1a, 0xab, 0x9d, 0xe6, - 0xb1, 0x51, 0x96, 0x50, 0x2c, 0x21, 0x79, 0x50, 0xd4, 0xdc, 0xab, 0xca, 0x71, 0x0e, 0x51, 0xa3, - 0xa8, 0xc9, 0x58, 0xce, 0x8a, 0x85, 0xf4, 0xef, 0xbe, 0xe6, 0xb6, 0x06, 0xb3, 0x49, 0xce, 0x8a, - 0x58, 0xfa, 0xb7, 0xf8, 0x62, 0xf0, 0xef, 0x56, 0xb7, 0xae, 0x67, 0x8e, 0x61, 0xd6, 0x11, 0xda, - 0xc7, 0xbb, 0x40, 0x05, 0xd5, 0xd7, 0x4d, 0xb7, 0x7e, 0xc2, 0xad, 0x27, 0x17, 0x32, 0x28, 0x7e, - 0x02, 0x49, 0x3f, 0xd1, 0xaa, 0x4f, 0xcc, 0xa6, 0x39, 0x2b, 0xe6, 0x72, 0xd4, 0xfc, 0x02, 0xe6, - 0xce, 0xbe, 0x6f, 0x7a, 0x2f, 0x94, 0x45, 0xf9, 0xb4, 0xf8, 0xbf, 0x3c, 0x28, 0xbd, 0xcd, 0xf2, - 0xc7, 0xa3, 0xdc, 0x4d, 0x88, 0x4b, 0x80, 0x15, 0x2a, 0x5b, 0x35, 0x2b, 0x6c, 0x6b, 0x7e, 0x0a, - 0xd1, 0x9b, 0xaa, 0x5c, 0xc6, 0xf6, 0x73, 0xbe, 0x29, 0x24, 0xa4, 0x03, 0x22, 0x91, 0x8c, 0x6e, - 0x09, 0xf9, 0x39, 0x24, 0xd5, 0x70, 0x0a, 0x05, 0x34, 0x0d, 0x68, 0xb8, 0x50, 0x8e, 0x7d, 0x7e, - 0x04, 0x31, 0x5a, 0xab, 0x6d, 0x30, 0x3e, 0x08, 0x71, 0x06, 0xf0, 0xac, 0xf5, 0x47, 0x67, 0xbc, - 0x8d, 0x3f, 0xf2, 0x10, 0xd7, 0x90, 0x0e, 0x53, 0xe3, 0xe6, 0x5d, 0x42, 0xec, 0x57, 0x42, 0x7b, - 0xb7, 0xdc, 0x44, 0xaf, 0x93, 0xae, 0x5e, 0xcf, 0xfc, 0xdf, 0x5d, 0x7d, 0x07, 0x00, 0x00, 0xff, - 0xff, 0x1f, 0xee, 0x62, 0x3e, 0xcf, 0x01, 0x00, 0x00, + // 283 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x41, 0x4b, 0xc3, 0x40, + 0x10, 0x85, 0xd9, 0x36, 0xad, 0xe9, 0x58, 0xa2, 0x2c, 0x22, 0xc1, 0x53, 0x58, 0x3d, 0x04, 0xc1, + 0x80, 0xf5, 0x1f, 0xa8, 0x88, 0xa2, 0x5e, 0xb6, 0xb7, 0xde, 0xb6, 0xc9, 0xd8, 0x88, 0x98, 0x5d, + 0x76, 0x36, 0x87, 0xde, 0xfd, 0xe1, 0x92, 0xcd, 0x9a, 0x82, 0xb4, 0xb7, 0x79, 0x33, 0xf3, 0xb1, + 0x6f, 0xde, 0xc2, 0x69, 0x5b, 0xbd, 0x23, 0x91, 0xda, 0x20, 0x15, 0xc6, 0x6a, 0xa7, 0xf9, 0xc4, + 0x28, 0x4b, 0x28, 0x16, 0x10, 0x3f, 0x2b, 0xaa, 0x9f, 0x54, 0xe9, 0x38, 0x87, 0xa8, 0x56, 0x54, + 0xa7, 0x2c, 0x63, 0xf9, 0x5c, 0xfa, 0xba, 0xeb, 0xb9, 0xad, 0xc1, 0x74, 0x94, 0xb1, 0x7c, 0x22, + 0x7d, 0x2d, 0x7e, 0x18, 0x1c, 0x3d, 0xe8, 0xc6, 0x75, 0xcc, 0x39, 0x4c, 0x5b, 0x42, 0xfb, 0xf2, + 0x18, 0xa8, 0xa0, 0xba, 0xbe, 0x69, 0xd7, 0xaf, 0xb8, 0xf5, 0xe4, 0x5c, 0x06, 0xc5, 0x2f, 0x20, + 0xee, 0x36, 0x1a, 0xf5, 0x8d, 0xe9, 0x38, 0x63, 0xf9, 0x4c, 0x0e, 0x9a, 0xdf, 0xc0, 0xcc, 0xd9, + 0xcf, 0x4d, 0xe7, 0x85, 0xd2, 0x28, 0x1b, 0xe7, 0xc7, 0x8b, 0x93, 0xc2, 0xdb, 0x2c, 0xfe, 0x3c, + 0xca, 0xdd, 0x86, 0xb8, 0x05, 0x58, 0xa2, 0xb2, 0x65, 0xbd, 0xc4, 0xa6, 0xe2, 0x97, 0x10, 0x7d, + 0xa8, 0xd2, 0xa5, 0x6c, 0x3f, 0xe7, 0x87, 0x42, 0x42, 0xd2, 0x23, 0x12, 0xc9, 0xe8, 0x86, 0x90, + 0x5f, 0x43, 0x5c, 0xf6, 0xa7, 0x50, 0x40, 0x93, 0x80, 0x86, 0x0b, 0xe5, 0x30, 0xe7, 0x67, 0x30, + 0x41, 0x6b, 0xb5, 0x0d, 0xc6, 0x7b, 0x21, 0xae, 0x00, 0xde, 0xb4, 0xfe, 0x6a, 0x8d, 0xb7, 0x71, + 0x20, 0x0f, 0xb1, 0x82, 0xa4, 0xdf, 0x1a, 0x5e, 0xde, 0x25, 0xc4, 0x0e, 0x26, 0x34, 0xfa, 0x97, + 0xd0, 0x5e, 0x07, 0xf7, 0xd1, 0x6a, 0xd4, 0x56, 0xeb, 0xa9, 0xff, 0xd7, 0xbb, 0xdf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x5a, 0xee, 0x38, 0xba, 0xeb, 0x01, 0x00, 0x00, } diff --git a/ud/udMessages.proto b/ud/udMessages.proto index c8f993c8cb08d5fbe40b9c9a782e5ddbc8e4da65..e6905f8359be96a7f331561495d44b20c7e1ab17 100644 --- a/ud/udMessages.proto +++ b/ud/udMessages.proto @@ -48,5 +48,6 @@ message LookupSend { // Message sent from UDB for looking up a user message LookupResponse { bytes pubKey = 1; + string username = 2; string error = 3; } diff --git a/ud/utils_test.go b/ud/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c04b9cb1579d65efae12c5191db0f07eaa8c8d07 --- /dev/null +++ b/ud/utils_test.go @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package ud + +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/comms/network" + "gitlab.com/elixxir/crypto/e2e" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "testing" + "time" +) + +func newTestNetworkManager(t *testing.T) interfaces.NetworkManager { + instanceComms := &connect.ProtoComms{ + Manager: connect.NewManagerTesting(t), + } + + thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), + getNDF(), nil, nil, t) + if err != nil { + t.Fatalf("Failed to create new test instance: %v", err) + } + + return &testNetworkManager{ + instance: thisInstance, + } +} + +// testNetworkManager is a test implementation of NetworkManager interface. +type testNetworkManager struct { + instance *network.Instance +} + +func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Single) ([]id.Round, e2e.MessageID, time.Time, error) { + return nil, e2e.MessageID{}, time.Time{}, nil +} + +func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) { + return nil, nil +} + +func (tnm *testNetworkManager) GetVerboseRounds() string { + return "" +} + +func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) { + return 0, ephemeral.Id{}, nil +} + +func (tnm *testNetworkManager) SendManyCMIX(map[id.ID]format.Message, params.CMIX) (id.Round, []ephemeral.Id, error) { + return 0, nil, nil +} + +type dummyEventMgr struct{} + +func (d *dummyEventMgr) Report(int, string, string, string) {} +func (tnm *testNetworkManager) GetEventManager() interfaces.EventManager { + return &dummyEventMgr{} +} + +func (tnm *testNetworkManager) GetInstance() *network.Instance { return tnm.instance } +func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { return nil } +func (tnm *testNetworkManager) Follow(interfaces.ClientErrorReport) (stoppable.Stoppable, error) { + return nil, nil +} +func (tnm *testNetworkManager) CheckGarbledMessages() {} +func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 } +func (tnm *testNetworkManager) GetSender() *gateway.Sender { return nil } +func (tnm *testNetworkManager) GetAddressSize() uint8 { return 0 } +func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { + return nil, nil +} +func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {} +func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter) {} + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + UDB: ndf.UDB{ + ID: id.DummyUser.Bytes(), + Cert: "", + Address: "address", + DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 49, 44, 34, + 70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58, + 51, 49, 54, 49, 50, 55, 48, 53, 56, 49, 51, 52, 50, 49, 54, 54, + 57, 52, 55, 125}, + }, + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" + + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" + + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" + + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" + + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" + + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" + + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" + + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" + + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" + + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" + + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" + + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" + + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" + + "5873847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" + + "F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" + + "264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" + + "9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" + + "B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" + + "0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" + + "92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" + + "2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" + + "995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" + + "9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" + + "1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" + + "8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" + + "C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" + + "5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" + + "59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" + + "2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" + + "B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +}