diff --git a/bindings/ud.go b/bindings/ud.go index 643066a2ad2417ab444698c63b717101454ea53c..a4c3cb5babca0236b546e5cb10fc74b10a98f0f3 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -10,6 +10,7 @@ package bindings import ( "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/ud" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" @@ -19,7 +20,7 @@ import ( //This package wraps the user discovery system // User Discovery object -type UserDiscovery struct{ +type UserDiscovery struct { ud *ud.Manager } @@ -30,13 +31,13 @@ type UserDiscovery struct{ // the bindings to think the other is in charge of the client object. // In general this is not an issue because the client object should exist // for the life of the program. -func NewUserDiscovery(client *Client)(*UserDiscovery, error){ - m, err := ud.NewManager(&client.api) +func NewUserDiscovery(client *Client) (*UserDiscovery, error) { + m, err := ud.NewManager(&client.api, &single.Manager{}) - if err!=nil{ + if err != nil { return nil, err - }else{ - return &UserDiscovery{ud:m}, nil + } else { + return &UserDiscovery{ud: m}, nil } } @@ -45,7 +46,7 @@ func NewUserDiscovery(client *Client)(*UserDiscovery, error){ // cannot be changed after registration at this time. Will fail if the user is // already registered. // Identity does not go over cmix, it occurs over normal communications -func (ud *UserDiscovery)Register(username string)error{ +func (ud *UserDiscovery) Register(username string) error { return ud.ud.Register(username) } @@ -57,10 +58,10 @@ func (ud *UserDiscovery)Register(username string)error{ // confirmation id instead. Over the communications system the fact is // associated with, a code will be sent. This confirmation ID needs to be // called along with the code to finalize the fact. -func (ud *UserDiscovery)AddFact(fStr string)(string, error){ +func (ud *UserDiscovery) AddFact(fStr string) (string, error) { f, err := fact.UnstringifyFact(fStr) - if err !=nil{ - return "", errors.WithMessage(err, "Failed to add due to " + + if err != nil { + return "", errors.WithMessage(err, "Failed to add due to "+ "malformed fact") } @@ -69,16 +70,16 @@ func (ud *UserDiscovery)AddFact(fStr string)(string, error){ // Confirms a fact first registered via AddFact. The confirmation ID comes from // AddFact while the code will come over the associated communications system -func (ud *UserDiscovery)ConfirmFact(confirmationID, code string)error{ +func (ud *UserDiscovery) ConfirmFact(confirmationID, code string) error { return ud.ud.SendConfirmFact(confirmationID, code) } // Removes a previously confirmed fact. Will fail if the passed fact string is // not well formed or if the fact is not associated with this client. -func (ud *UserDiscovery)RemoveFact(fStr string)error{ +func (ud *UserDiscovery) RemoveFact(fStr string) error { f, err := fact.UnstringifyFact(fStr) - if err !=nil{ - return errors.WithMessage(err, "Failed to remove due to " + + if err != nil { + return errors.WithMessage(err, "Failed to remove due to "+ "malformed fact") } return ud.ud.RemoveFact(f) @@ -96,20 +97,20 @@ type SearchCallback interface { // This is NOT intended to be used to search for multiple users at once, that // can have a privacy reduction. Instead, it is intended to be used to search // for a user where multiple pieces of information is known. -func (ud UserDiscovery)Search(fl string, callback SearchCallback, - timeoutMS int)error{ +func (ud UserDiscovery) Search(fl string, callback SearchCallback, + timeoutMS int) error { factList, _, err := fact.UnstringifyFactList(fl) - if err!=nil{ - return errors.WithMessage(err, "Failed to search due to " + + if err != nil { + return errors.WithMessage(err, "Failed to search due to "+ "malformed fact list") } - timeout := time.Duration(timeoutMS)*time.Millisecond - cb := func(cl []contact.Contact, err error){ + timeout := time.Duration(timeoutMS) * time.Millisecond + cb := func(cl []contact.Contact, err error) { var contactList *ContactList var errStr string - if err==nil{ - contactList = &ContactList{list:cl} - }else{ + if err == nil { + contactList = &ContactList{list: cl} + } else { errStr = err.Error() } callback.Callback(contactList, errStr) @@ -128,23 +129,23 @@ type SingleSearchCallback interface { // a list of contacts, each having the facts it hit against. // This only searches for a single fact at a time. It is intended to make some // simple use cases of the API easier. -func (ud UserDiscovery)SearchSingle(f string, callback SingleSearchCallback, - timeoutMS int)error{ +func (ud UserDiscovery) SearchSingle(f string, callback SingleSearchCallback, + timeoutMS int) error { fObj, err := fact.UnstringifyFact(f) - if err!=nil{ - return errors.WithMessage(err, "Failed to single search due " + + if err != nil { + return errors.WithMessage(err, "Failed to single search due "+ "to malformed fact") } - timeout := time.Duration(timeoutMS)*time.Millisecond - cb := func(cl []contact.Contact, err error){ - var contact *Contact + timeout := time.Duration(timeoutMS) * time.Millisecond + cb := func(cl []contact.Contact, err error) { + var c *Contact var errStr string - if err==nil{ - contact = &Contact{c:&cl[0]} - }else{ + if err == nil { + c = &Contact{c: &cl[0]} + } else { errStr = err.Error() } - callback.Callback(contact, errStr) + callback.Callback(c, errStr) } return ud.ud.Search([]fact.Fact{fObj}, cb, timeout) } @@ -158,27 +159,27 @@ type LookupCallback interface { // id is the byte representation of an id. // This will reject if that id is malformed. The LookupCallback will return // the associated contact if it exists. -func (ud UserDiscovery)Lookup(idBytes []byte, callback LookupCallback, - timeoutMS int)error { +func (ud UserDiscovery) Lookup(idBytes []byte, callback LookupCallback, + timeoutMS int) error { uid, err := id.Unmarshal(idBytes) - if err!=nil{ - return errors.WithMessage(err, "Failed to lookup due to " + + if err != nil { + return errors.WithMessage(err, "Failed to lookup due to "+ "malformed id") } - timeout := time.Duration(timeoutMS)*time.Millisecond - cb := func(cl contact.Contact, err error){ - var contact *Contact + timeout := time.Duration(timeoutMS) * time.Millisecond + cb := func(cl contact.Contact, err error) { + var c *Contact var errStr string - if err==nil{ - contact = &Contact{c:&cl} - }else{ + if err == nil { + c = &Contact{c: &cl} + } else { errStr = err.Error() } - callback.Callback(contact, errStr) + callback.Callback(c, errStr) } return ud.ud.Lookup(uid, cb, timeout) -} \ No newline at end of file +} diff --git a/cmd/single.go b/cmd/single.go index a7c8fafcc17af98cd355bdbbc2de807fd2f48432..92a3027a8501084752ff2f5acd8f1a298782a422 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -128,7 +128,7 @@ func init() { } // sendSingleUse sends a single use message. -func sendSingleUse(m *single.Manager, partner *contact.Contact, payload []byte, +func sendSingleUse(m *single.Manager, partner contact.Contact, payload []byte, maxMessages uint8, timeout time.Duration) { // Construct callback callbackChan := make(chan struct { @@ -238,7 +238,7 @@ func makeResponsePayloadPart(m *single.Manager, payload []byte) []byte { // readSingleUseContact opens the contact specified in the CLI flags. Panics if // no file provided or if an error occurs while reading or unmarshalling it. -func readSingleUseContact(key string) *contact.Contact { +func readSingleUseContact(key string) contact.Contact { // Get path filePath := viper.GetString(key) if filePath == "" { @@ -258,5 +258,5 @@ func readSingleUseContact(key string) *contact.Contact { jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err) } - return &c + return c } diff --git a/cmd/ud.go b/cmd/ud.go index c267a68edd50761d936430bee678bf8a0e6dab6a..3c890c2e39c300901df26e164d448b219c41299c 100644 --- a/cmd/ud.go +++ b/cmd/ud.go @@ -15,47 +15,47 @@ import ( "github.com/spf13/viper" "gitlab.com/elixxir/client/interfaces/contact" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/client/ud" "gitlab.com/elixxir/primitives/fact" "time" ) -// udCmd user discovery subcommand, allowing user lookup and registration for -// allowing others to search. -// This basically runs a client for these functions with the UD module enabled. -// Normally, clients don't need it so it is not loaded for the rest of the -// commands. +// udCmd is the user discovery subcommand, which allows for user lookup, +// registration, and search. This basically runs a client for these functions +// with the UD module enabled. Normally, clients do not need it so it is not +// loaded for the rest of the commands. var udCmd = &cobra.Command{ - Use: "ud", - Short: ("Register for & search users using the xxnet user discovery " + - "service"), - Args: cobra.NoArgs, + Use: "ud", + Short: "Register for and search users using the xx network user discovery service.", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { client := initClient() + + // Get user and save contact to file user := client.GetUser() jww.INFO.Printf("User: %s", user.ReceptionID) writeContact(user.GetContact()) // Set up reception handler - swboard := client.GetSwitchboard() + swBoard := client.GetSwitchboard() recvCh := make(chan message.Receive, 10000) - listenerID := swboard.RegisterChannel("DefaultCLIReceiver", + listenerID := swBoard.RegisterChannel("DefaultCLIReceiver", switchboard.AnyUser(), message.Text, recvCh) jww.INFO.Printf("Message ListenerID: %v", listenerID) - // Set up auth request handler, which simply prints the - // user id of the requestor. + // Set up auth request handler, which simply prints the user ID of the + // requester authMgr := client.GetAuthRegistrar() authMgr.AddGeneralRequestCallback(printChanRequest) // If unsafe channels, add auto-acceptor if viper.GetBool("unsafe-channel-creation") { authMgr.AddGeneralRequestCallback(func( - requestor contact.Contact, message string) { - jww.INFO.Printf("Got Request: %s", requestor.ID) - err := client.ConfirmAuthenticatedChannel( - requestor) + requester contact.Contact, message string) { + jww.INFO.Printf("Got Request: %s", requester.ID) + err := client.ConfirmAuthenticatedChannel(requester) if err != nil { jww.FATAL.Panicf("%+v", err) } @@ -72,17 +72,21 @@ var udCmd = &cobra.Command{ client.GetHealth().AddChannel(connected) waitUntilConnected(connected) - userDiscoveryMgr, err := ud.NewManager(client) + // Make single-use manager and start receiving process + singleMng := single.NewManager(client) + client.AddService(singleMng.StartProcesses) + + // Make user discovery manager + userDiscoveryMgr, err := ud.NewManager(client, singleMng) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new UD manager: %+v", err) } - userDiscoveryMgr.StartProcesses() userToRegister := viper.GetString("register") if userToRegister != "" { err = userDiscoveryMgr.Register(userToRegister) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to register user %s: %+v", userToRegister, err) } } @@ -91,15 +95,16 @@ var udCmd = &cobra.Command{ if phone != "" { f, err := fact.NewFact(fact.Phone, phone) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new fact: %+v", err) } newFacts = append(newFacts, f) } + email := viper.GetString("addemail") if email != "" { f, err := fact.NewFact(fact.Email, email) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new fact: %+v", err) } newFacts = append(newFacts, f) } @@ -107,7 +112,7 @@ var udCmd = &cobra.Command{ for i := 0; i < len(newFacts); i++ { r, err := userDiscoveryMgr.SendRegisterFact(newFacts[i]) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to send register fact: %+v", err) } // TODO Store the code? jww.INFO.Printf("Fact Add Response: %+v", r) @@ -115,9 +120,8 @@ var udCmd = &cobra.Command{ confirmID := viper.GetString("confirm") if confirmID != "" { - // TODO Lookup code - err = userDiscoveryMgr.SendConfirmFact(confirmID, - confirmID) + // TODO: Lookup code + err = userDiscoveryMgr.SendConfirmFact(confirmID, confirmID) if err != nil { jww.FATAL.Panicf("%+v", err) } @@ -127,53 +131,56 @@ var udCmd = &cobra.Command{ if lookupIDStr != "" { lookupID, ok := parseRecipient(lookupIDStr) if !ok { - jww.FATAL.Panicf("Could not parse: %s", - lookupIDStr) + jww.FATAL.Panicf("Could not parse recipient: %s", lookupIDStr) } - userDiscoveryMgr.Lookup(lookupID, + err = userDiscoveryMgr.Lookup(lookupID, func(newContact contact.Contact, err error) { if err != nil { jww.FATAL.Panicf("%+v", err) } cBytes := newContact.Marshal() - if err != nil { - jww.FATAL.Panicf("%+v", err) - } fmt.Printf(string(cBytes)) - }, - time.Duration(90*time.Second)) + }, 90*time.Second) + + if err != nil { + jww.WARN.Printf("Failed UD lookup: %+v", err) + } + time.Sleep(91 * time.Second) } - usernameSrchStr := viper.GetString("searchusername") - emailSrchStr := viper.GetString("searchemail") - phoneSrchStr := viper.GetString("searchphone") + usernameSearchStr := viper.GetString("searchusername") + emailSearchStr := viper.GetString("searchemail") + phoneSearchStr := viper.GetString("searchphone") var facts fact.FactList - if usernameSrchStr != "" { - f, err := fact.NewFact(fact.Username, usernameSrchStr) + if usernameSearchStr != "" { + f, err := fact.NewFact(fact.Username, usernameSearchStr) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new fact: %+v", err) } facts = append(facts, f) } - if emailSrchStr != "" { - f, err := fact.NewFact(fact.Email, emailSrchStr) + if emailSearchStr != "" { + f, err := fact.NewFact(fact.Email, emailSearchStr) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new fact: %+v", err) } facts = append(facts, f) } - if phoneSrchStr != "" { - f, err := fact.NewFact(fact.Phone, phoneSrchStr) + if phoneSearchStr != "" { + f, err := fact.NewFact(fact.Phone, phoneSearchStr) if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Failed to create new fact: %+v", err) } facts = append(facts, f) } if len(facts) == 0 { - client.StopNetworkFollower(10 * time.Second) + err = client.StopNetworkFollower(10 * time.Second) + if err != nil { + jww.WARN.Print(err) + } return } @@ -184,9 +191,6 @@ var udCmd = &cobra.Command{ } for i := 0; i < len(contacts); i++ { cBytes := contacts[i].Marshal() - if err != nil { - jww.FATAL.Panicf("%+v", err) - } jww.INFO.Printf("Size Printed: %d", len(cBytes)) fmt.Printf("%s", cBytes) } @@ -195,42 +199,45 @@ var udCmd = &cobra.Command{ jww.FATAL.Panicf("%+v", err) } time.Sleep(91 * time.Second) - client.StopNetworkFollower(90 * time.Second) + err = client.StopNetworkFollower(90 * time.Second) + if err != nil { + jww.WARN.Print(err) + } }, } func init() { // User Discovery subcommand Options udCmd.Flags().StringP("register", "r", "", - "Register this user with user discovery") - viper.BindPFlag("register", - udCmd.Flags().Lookup("register")) - udCmd.Flags().StringP("addphone", "", "", + "Register this user with user discovery.") + _ = viper.BindPFlag("register", udCmd.Flags().Lookup("register")) + + udCmd.Flags().String("addphone", "", "Add phone number to existing user registration.") - viper.BindPFlag("addphone", udCmd.Flags().Lookup("addphone")) + _ = viper.BindPFlag("addphone", udCmd.Flags().Lookup("addphone")) + udCmd.Flags().StringP("addemail", "e", "", "Add email to existing user registration.") - viper.BindPFlag("addemail", udCmd.Flags().Lookup("addemail")) - udCmd.Flags().StringP("confirm", "", "", - "Confirm fact with confirmation id") - viper.BindPFlag("confirm", udCmd.Flags().Lookup("confirm")) + _ = viper.BindPFlag("addemail", udCmd.Flags().Lookup("addemail")) + + udCmd.Flags().String("confirm", "", "Confirm fact with confirmation ID.") + _ = viper.BindPFlag("confirm", udCmd.Flags().Lookup("confirm")) udCmd.Flags().StringP("lookup", "u", "", - "Look up user ID. Use '0x' or 'b64:' for hex and base64 "+ - "representations") - viper.BindPFlag("lookup", udCmd.Flags().Lookup("lookup")) - udCmd.Flags().StringP("searchusername", "", "", - "Search for users with this username") - viper.BindPFlag("searchusername", - udCmd.Flags().Lookup("searchusername")) - udCmd.Flags().StringP("searchemail", "", "", - "Search for users with this email address") - viper.BindPFlag("searchemail", - udCmd.Flags().Lookup("searchemail")) - udCmd.Flags().StringP("searchphone", "", "", - "Search for users with this email address") - viper.BindPFlag("searchphone", - udCmd.Flags().Lookup("searchphone")) + "Look up user ID. Use '0x' or 'b64:' for hex and base64 representations.") + _ = viper.BindPFlag("lookup", udCmd.Flags().Lookup("lookup")) + + udCmd.Flags().String("searchusername", "", + "Search for users with this username.") + _ = viper.BindPFlag("searchusername", udCmd.Flags().Lookup("searchusername")) + + udCmd.Flags().String("searchemail", "", + "Search for users with this email address.") + _ = viper.BindPFlag("searchemail", udCmd.Flags().Lookup("searchemail")) + + udCmd.Flags().String("searchphone", "", + "Search for users with this email address.") + _ = viper.BindPFlag("searchphone", udCmd.Flags().Lookup("searchphone")) rootCmd.AddCommand(udCmd) } diff --git a/go.mod b/go.mod index 0792504a91217fe7d064204d8894e114382df4a5..27971d0f1c7c812a4106d9d8c7692f2b1bf96cd3 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( gitlab.com/elixxir/primitives v0.0.3-0.20210216174458-2a23825c1eb1 gitlab.com/xx_network/comms v0.0.4-0.20210216174438-0790d1f1f225 gitlab.com/xx_network/crypto v0.0.5-0.20210216174356-e81e1ddf8fb7 - gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f + gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect diff --git a/go.sum b/go.sum index 0d535ad56dce99718be355c34655e8d41088e32e..287f8dadf43fc313b4686ec4aacb8fe54c36310c 100644 --- a/go.sum +++ b/go.sum @@ -299,6 +299,10 @@ gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12 h1:dOQS9tz gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f h1:0wFEYIHuPkWJuDkbDXNrwC5yGwkd7Mugt2BwcTqQbFY= gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= +gitlab.com/xx_network/primitives v0.0.4-0.20210219220414-99c389307a84 h1:dvc/2RMlgffJHy2mJd+GK9jhWLni42o5ChFPZ2oYgkw= +gitlab.com/xx_network/primitives v0.0.4-0.20210219220414-99c389307a84/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= +gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36 h1:41qeW7XB9Rllsi6fe37+eaQCLjTGchpvcJqwEvZxeXE= +gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0= gitlab.com/xx_network/ring v0.0.2/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go index 09f890b3b834f0de3b36addd1f94019ae8e05d19..102b4b7d53205670e605eaff99402081b4651365 100644 --- a/interfaces/contact/contact.go +++ b/interfaces/contact/contact.go @@ -150,3 +150,11 @@ func Unmarshal(b []byte) (Contact, error) { return c, nil } + +// Equal determines if the two contacts have the same values. +func Equal(a, b Contact) bool { + return a.ID.Cmp(b.ID) && + a.DhPubKey.Cmp(b.DhPubKey) == 0 && + bytes.Equal(a.OwnershipProof, b.OwnershipProof) && + a.Facts.Stringify() == b.Facts.Stringify() +} diff --git a/interfaces/contact/contact_test.go b/interfaces/contact/contact_test.go index b87f983ca8d055de6da893d23f9d8ab8382c01a8..871b2f9bf863b071dbeeed52b862bbd1ca71954c 100644 --- a/interfaces/contact/contact_test.go +++ b/interfaces/contact/contact_test.go @@ -174,7 +174,42 @@ func TestContact_GetFingerprint_Consistency(t *testing.T) { } } +} + +// Happy path. +func TestEqual(t *testing.T) { + a := Contact{ + ID: id.NewIdFromUInt(rand.Uint64(), id.User, t), + DhPubKey: getCycInt(512), + OwnershipProof: make([]byte, 1024), + Facts: fact.FactList{ + {Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUsername", T: fact.Username}, + {Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongEmail@elixxir.io", T: fact.Email}, + {Fact: "6502530000US", T: fact.Phone}, + }, + } + rand.Read(a.OwnershipProof) + b := Contact{ + ID: a.ID, + DhPubKey: a.DhPubKey, + OwnershipProof: a.OwnershipProof, + Facts: a.Facts, + } + c := Contact{ + ID: id.NewIdFromUInt(rand.Uint64(), id.User, t), + DhPubKey: getCycInt(512), + OwnershipProof: make([]byte, 1024), + } + if !Equal(a, b) { + t.Errorf("Equal reported two equal contacts as different."+ + "\na: %+v\nb: +%v", a, b) + } + + if Equal(a, c) { + t.Errorf("Equal reported two unequal contacts as the same."+ + "\na: %+v\nc: +%v", a, c) + } } func getCycInt(size int) *cyclic.Int { diff --git a/single/callbackMap.go b/single/callbackMap.go index be7b03380285b6d298f7b3344bfff08ce16228b0..39bd3cc166d96e8c3a712a39726b0c5d1cdf820a 100644 --- a/single/callbackMap.go +++ b/single/callbackMap.go @@ -13,7 +13,7 @@ import ( "sync" ) -type receiveComm func(payload []byte, c Contact) +type ReceiveComm func(payload []byte, c Contact) // callbackMap stores a list of possible callbacks that can be called when a // message is received. To receive a transmission, each transmitted message must @@ -22,21 +22,21 @@ type receiveComm func(payload []byte, c Contact) // message belongs to. The tag can be anything, but should be long enough so // that it is unique. type callbackMap struct { - callbacks map[singleUse.TagFP]receiveComm + callbacks map[singleUse.TagFP]ReceiveComm sync.RWMutex } // newCallbackMap initialises a new map. func newCallbackMap() *callbackMap { return &callbackMap{ - callbacks: map[singleUse.TagFP]receiveComm{}, + callbacks: map[singleUse.TagFP]ReceiveComm{}, } } // registerCallback adds a callback function to the map that associates it with // its tag. The tag should be a unique string identifying the module using the // callback. -func (cbm *callbackMap) registerCallback(tag string, callback receiveComm) { +func (cbm *callbackMap) registerCallback(tag string, callback ReceiveComm) { cbm.Lock() defer cbm.Unlock() @@ -46,7 +46,7 @@ func (cbm *callbackMap) registerCallback(tag string, callback receiveComm) { // getCallback returns the callback registered with the given tag fingerprint. // An error is returned if no associated callback exists. -func (cbm *callbackMap) getCallback(tagFP singleUse.TagFP) (receiveComm, error) { +func (cbm *callbackMap) getCallback(tagFP singleUse.TagFP) (ReceiveComm, error) { cbm.RLock() defer cbm.RUnlock() diff --git a/single/callbackMap_test.go b/single/callbackMap_test.go index 59ccb2dde618ebf9256cb82dd27aebb5d4d79903..5af02c8cac5b5b64f372a391ecb031b515f52298 100644 --- a/single/callbackMap_test.go +++ b/single/callbackMap_test.go @@ -18,7 +18,7 @@ func Test_callbackMap_registerCallback(t *testing.T) { callbackChan := make(chan int) testCallbacks := []struct { tag string - cb receiveComm + cb ReceiveComm }{ {"tag1", func([]byte, Contact) { callbackChan <- 0 }}, {"tag2", func([]byte, Contact) { callbackChan <- 1 }}, @@ -45,7 +45,7 @@ func Test_callbackMap_getCallback(t *testing.T) { callbackChan := make(chan int) testCallbacks := []struct { tagFP singleUse.TagFP - cb receiveComm + cb ReceiveComm }{ {singleUse.UnmarshalTagFP([]byte("tag1")), func([]byte, Contact) { callbackChan <- 0 }}, {singleUse.UnmarshalTagFP([]byte("tag2")), func([]byte, Contact) { callbackChan <- 1 }}, diff --git a/single/contact.go b/single/contact.go index 25d207ddebb3fe36faffb03032a639c26b9ff271..bf0599899609261f38475e0ee5eed245898709ae 100644 --- a/single/contact.go +++ b/single/contact.go @@ -29,9 +29,9 @@ func NewContact(partner *id.ID, partnerPubKey, dhKey *cyclic.Int, tagFP singleUse.TagFP, maxParts uint8) Contact { used := int32(0) return Contact{ - partner: partner.DeepCopy(), - partnerPubKey: partnerPubKey.DeepCopy(), - dhKey: dhKey.DeepCopy(), + partner: partner, + partnerPubKey: partnerPubKey, + dhKey: dhKey, tagFP: tagFP, maxParts: maxParts, used: &used, diff --git a/single/manager.go b/single/manager.go index cbf21d9524799129d96139c727a3371b54592f16..78481829180017f242ca45e43b2bacc4175cf4dd 100644 --- a/single/manager.go +++ b/single/manager.go @@ -91,7 +91,7 @@ func (m *Manager) StartProcesses() stoppable.Stoppable { } // RegisterCallback registers a callback for received messages. -func (m *Manager) RegisterCallback(tag string, callback receiveComm) { +func (m *Manager) RegisterCallback(tag string, callback ReceiveComm) { jww.DEBUG.Printf("Registering single-use callback with tag %s.", tag) m.callbackMap.registerCallback(tag, callback) } diff --git a/single/manager_test.go b/single/manager_test.go index d2dcfa4beae708c435532589f5dc42497b682f77..33427010d53d967a2a2dd52fb1971b661974e2d9 100644 --- a/single/manager_test.go +++ b/single/manager_test.go @@ -57,7 +57,7 @@ func Test_newManager(t *testing.T) { // Happy path. func TestManager_StartProcesses(t *testing.T) { m := newTestManager(0, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -143,7 +143,7 @@ func TestManager_StartProcesses(t *testing.T) { // Happy path: tests that the stoppable stops both routines. func TestManager_StartProcesses_Stop(t *testing.T) { m := newTestManager(0, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -225,7 +225,7 @@ type receiveCommData struct { c Contact } -func createReceiveComm() (receiveComm, chan receiveCommData) { +func createReceiveComm() (ReceiveComm, chan receiveCommData) { callbackChan := make(chan receiveCommData) callback := func(payload []byte, c Contact) { diff --git a/single/reception_test.go b/single/reception_test.go index 5230f364533d183a8007ee465b47a9ef07cca1cd..6989406621f2300e2f78c057defaf55d2e5858e1 100644 --- a/single/reception_test.go +++ b/single/reception_test.go @@ -17,7 +17,7 @@ func TestManager_receiveTransmissionHandler(t *testing.T) { m := newTestManager(0, false, t) rawMessages := make(chan message.Receive, rawMessageBuffSize) quitChan := make(chan struct{}) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -82,7 +82,7 @@ func TestManager_receiveTransmissionHandler_FingerPrintError(t *testing.T) { m := newTestManager(0, false, t) rawMessages := make(chan message.Receive, rawMessageBuffSize) quitChan := make(chan struct{}) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(42), } @@ -119,7 +119,7 @@ func TestManager_receiveTransmissionHandler_ProcessMessageError(t *testing.T) { m := newTestManager(0, false, t) rawMessages := make(chan message.Receive, rawMessageBuffSize) quitChan := make(chan struct{}) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -158,7 +158,7 @@ func TestManager_receiveTransmissionHandler_TagFpError(t *testing.T) { m := newTestManager(0, false, t) rawMessages := make(chan message.Receive, rawMessageBuffSize) quitChan := make(chan struct{}) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -181,7 +181,7 @@ func TestManager_receiveTransmissionHandler_TagFpError(t *testing.T) { // Happy path. func TestManager_processTransmission(t *testing.T) { m := newTestManager(0, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("partnerID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } @@ -235,7 +235,7 @@ func TestManager_processTransmission_TransmitMessageUnmarshalError(t *testing.T) // Error path: MAC fails to verify. func TestManager_processTransmission_MacVerifyError(t *testing.T) { m := newTestManager(0, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("partnerID", id.User, t), DhPubKey: m.store.E2e().GetDHPublicKey(), } diff --git a/single/singleUseMap.go b/single/singleUseMap.go index 48c3f096f864d5d94e88cf1d7e4cd34b36811f07..01e1cca5b6b5472dc75063cf79f20b25e3cb65da 100644 --- a/single/singleUseMap.go +++ b/single/singleUseMap.go @@ -23,7 +23,7 @@ type pending struct { sync.RWMutex } -type replyComm func(payload []byte, err error) +type ReplyComm func(payload []byte, err error) // state contains the information and state of each single-use message that is // being transmitted. @@ -31,7 +31,7 @@ type state struct { dhKey *cyclic.Int fpMap *fingerprintMap // List of fingerprints for each response part c *collator // Collects all response message parts - callback replyComm // Returns the error status of the communication + callback ReplyComm // Returns the error status of the communication quitChan chan struct{} // Sending on channel kills the timeout handler } @@ -44,7 +44,7 @@ func newPending() *pending { // newState generates a new state object with the fingerprint map and collator // initialised. -func newState(dhKey *cyclic.Int, messageCount uint8, callback replyComm) *state { +func newState(dhKey *cyclic.Int, messageCount uint8, callback ReplyComm) *state { return &state{ dhKey: dhKey, fpMap: newFingerprintMap(dhKey, uint64(messageCount)), @@ -57,7 +57,7 @@ func newState(dhKey *cyclic.Int, messageCount uint8, callback replyComm) *state // addState adds a new state to the map and starts a thread waiting for all the // message parts or for the timeout to occur. func (p *pending) addState(rid *id.ID, dhKey *cyclic.Int, maxMsgs uint8, - callback replyComm, timeout time.Duration) (chan struct{}, *int32, error) { + callback ReplyComm, timeout time.Duration) (chan struct{}, *int32, error) { p.Lock() // Check if the state already exists @@ -84,7 +84,7 @@ func (p *pending) addState(rid *id.ID, dhKey *cyclic.Int, maxMsgs uint8, // timeoutHandler waits for the signal to complete or times out and deletes the // state. -func (p *pending) timeoutHandler(rid *id.ID, callback replyComm, +func (p *pending) timeoutHandler(rid *id.ID, callback ReplyComm, timeout time.Duration, quitChan chan struct{}, quit *int32) { jww.DEBUG.Printf("Starting handler for sending single-use transmission "+ "that will timeout after %s.", timeout) diff --git a/single/transmission.go b/single/transmission.go index 2e5493d34ee19149ba04cb65c6ad4d5a3203ac54..13abe66dbd753c5f1b2e6a3036c0c6efa8f0c9b4 100644 --- a/single/transmission.go +++ b/single/transmission.go @@ -43,8 +43,8 @@ func (m *Manager) GetMaxTransmissionPayloadSize() int { } // TransmitSingleUse creates a CMIX message, sends it, and waits for delivery. -func (m *Manager) TransmitSingleUse(partner *contact2.Contact, payload []byte, - tag string, maxMsgs uint8, callback replyComm, timeout time.Duration) error { +func (m *Manager) TransmitSingleUse(partner contact2.Contact, payload []byte, + tag string, maxMsgs uint8, callback ReplyComm, timeout time.Duration) error { rngReader := m.rng.GetStream() defer rngReader.Close() @@ -60,9 +60,8 @@ type roundEvents interface { } // transmitSingleUse has the fields passed in for easier testing. -func (m *Manager) transmitSingleUse(partner *contact2.Contact, payload []byte, - tag string, MaxMsgs uint8, rng io.Reader, callback replyComm, - timeout time.Duration, roundEvents roundEvents) error { +func (m *Manager) transmitSingleUse(partner contact2.Contact, payload []byte, + tag string, MaxMsgs uint8, rng io.Reader, callback ReplyComm, timeout time.Duration, roundEvents roundEvents) error { // Get ephemeral ID address size; this will block until the client knows the // address size if it is currently unknown @@ -174,7 +173,7 @@ func (m *Manager) transmitSingleUse(partner *contact2.Contact, payload []byte, // makeTransmitCmixMessage generates a CMIX message containing the transmission message, // which contains the encrypted payload. -func (m *Manager) makeTransmitCmixMessage(partner *contact2.Contact, +func (m *Manager) makeTransmitCmixMessage(partner contact2.Contact, payload []byte, tag string, maxMsgs uint8, addressSize uint, timeout time.Duration, timeNow time.Time, rng io.Reader) (format.Message, *cyclic.Int, *id.ID, ephemeral.Id, error) { diff --git a/single/transmission_test.go b/single/transmission_test.go index 82fc55376d1da759c6ff7c1b98f63b9da70ae4eb..fe6df60ba188ef5abe88e06ab4434c0d289618b5 100644 --- a/single/transmission_test.go +++ b/single/transmission_test.go @@ -38,7 +38,7 @@ func TestManager_GetMaxTransmissionPayloadSize(t *testing.T) { func TestManager_transmitSingleUse(t *testing.T) { m := newTestManager(0, false, t) prng := rand.New(rand.NewSource(42)) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -84,7 +84,7 @@ func TestManager_transmitSingleUse(t *testing.T) { // Error path: function quits early if the timoutHandler quit. func TestManager_transmitSingleUse_QuitChanError(t *testing.T) { m := newTestManager(10*time.Millisecond, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -117,7 +117,7 @@ func TestManager_transmitSingleUse_QuitChanError(t *testing.T) { func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) { timeout := 15 * time.Millisecond m := newTestManager(timeout, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -149,7 +149,7 @@ func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) { // Error path: SendCMIX fails to send message. func TestManager_transmitSingleUse_SendCMIXError(t *testing.T) { m := newTestManager(0, true, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -181,7 +181,7 @@ func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) { prng := rand.New(rand.NewSource(42)) payload := make([]byte, m.store.Cmix().GetGroup().GetP().ByteLen()) - err := m.transmitSingleUse(nil, payload, "", 0, prng, nil, 0, nil) + err := m.transmitSingleUse(contact2.Contact{}, payload, "", 0, prng, nil, 0, nil) if err == nil { t.Error("transmitSingleUse() did not return an error when the payload " + "is too large.") @@ -191,7 +191,7 @@ func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) { // Error path: failed to add pending state because is already exists. func TestManager_transmitSingleUse_AddStateError(t *testing.T) { m := newTestManager(0, false, t) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -222,7 +222,7 @@ func TestManager_transmitSingleUse_AddStateError(t *testing.T) { func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) { m := newTestManager(0, false, t) prng := rand.New(rand.NewSource(42)) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("Contact ID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(5), } @@ -254,7 +254,7 @@ func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) { func TestManager_makeTransmitCmixMessage(t *testing.T) { m := newTestManager(0, false, t) prng := rand.New(rand.NewSource(42)) - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(42), } @@ -324,7 +324,7 @@ func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) { payload := make([]byte, 1000) rand.New(rand.NewSource(42)).Read(payload) - _, _, _, _, err := m.makeTransmitCmixMessage(nil, payload, "", 8, 32, + _, _, _, _, err := m.makeTransmitCmixMessage(contact2.Contact{}, payload, "", 8, 32, 30*time.Second, time.Now(), prng) if !check(err, "too long for message payload capacity") { @@ -337,7 +337,7 @@ func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) { func TestManager_makeTransmitCmixMessage_KeyGenerationError(t *testing.T) { m := newTestManager(0, false, t) prng := strings.NewReader("a") - partner := &contact2.Contact{ + partner := contact2.Contact{ ID: id.NewIdFromString("recipientID", id.User, t), DhPubKey: m.store.E2e().GetGroup().NewInt(42), } diff --git a/ud/commID.go b/ud/commID.go deleted file mode 100644 index 018db5dc5f2a8023df465fac68610bd9ca87effe..0000000000000000000000000000000000000000 --- a/ud/commID.go +++ /dev/null @@ -1,52 +0,0 @@ -package ud - -import ( - "encoding/binary" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/storage/versioned" - "time" -) - -const commIDKey = "commIDKey" -const commIDVersion = 0 - -// getCommID returns the ID for the next comm. IDs are generated sequentially. -func (m *Manager) getCommID() uint64 { - - m.commIDLock.Lock() - defer m.commIDLock.Unlock() - returnedID := m.commID - - // Increment ID for next get - m.commID++ - - // Save ID storage - data := make([]byte, 8) - binary.BigEndian.PutUint64(data, m.commID) - - obj := &versioned.Object{ - Version: commIDVersion, - Timestamp: time.Now(), - Data: data, - } - - if err := m.storage.Set(commIDKey, obj); err != nil { - jww.FATAL.Panicf("Failed to store the next commID: %+v", err) - } - - return returnedID -} - -// loadCommID retrieves the next comm ID from storage. -func (m *Manager) loadCommID() { - m.commIDLock.Lock() - defer m.commIDLock.Unlock() - - obj, err := m.storage.Get(commIDKey) - if err != nil { - jww.WARN.Printf("Failed to get the commID; restarting at zero: %s", err) - return - } - - m.commID = binary.BigEndian.Uint64(obj.Data) -} diff --git a/ud/lookup.go b/ud/lookup.go index 3d57fcda5023bb6f6d9dfa2efa291a0cf7cfe10f..d7ce0fa69f2addc3bb09419f98ec60b3a6cc2985 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -6,161 +6,72 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/contact" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/comms/network/dataStructures" - "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" "time" ) -type lookupCallback func(contact.Contact, error) - -func (m *Manager) lookupProcess(c chan message.Receive, quitCh <-chan struct{}) { - for true { - select { - case <-quitCh: - return - case response := <-c: - - // Unmarshal the message - lookupResponse := &LookupResponse{} - if err := proto.Unmarshal(response.Payload, lookupResponse); err != nil { - jww.WARN.Printf("Dropped a lookup response from user "+ - "discovery due to failed unmarshal: %s", err) - } +// LookupTag specifies which callback to trigger when UD receives a lookup +// request. +const LookupTag = "xxNetwork_UdLookup" - // Get the appropriate channel from the lookup - m.inProgressLookupMux.RLock() - ch, ok := m.inProgressLookup[lookupResponse.CommID] - m.inProgressLookupMux.RUnlock() - if !ok { - jww.WARN.Printf("Dropped a lookup response from user "+ - "discovery due to unknown comm ID: %d", - lookupResponse.CommID) - } +// TODO: reconsider where this comes from +const maxLookupMessages = 20 - // Send the response on the correct channel - // Drop if the send cannot be completed - select { - case ch <- lookupResponse: - default: - jww.WARN.Printf("Dropped a lookup response from user "+ - "discovery due to failure to transmit to handling thread: "+ - "commID: %d", lookupResponse.CommID) - } - } - } -} +type lookupCallback func(contact.Contact, error) // Lookup returns the public key of the passed ID as known by the user discovery // system or returns by the timeout. func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout) - if !m.IsRegistered(){ - return errors.New("Failed to lookup: " + - "client is not registered") - } - - // Get the ID of this comm so it can be connected to its response - commID := m.getCommID() - - // Build the request - request := &LookupSend{ - UserID: uid.Marshal(), - CommID: commID, + if !m.IsRegistered() { + return errors.New("Failed to lookup: client is not registered.") } + // Build the request and marshal it + request := &LookupSend{UserID: uid.Marshal()} requestMarshaled, err := proto.Marshal(request) if err != nil { - return errors.WithMessage(err, "Failed to form outgoing request") + return errors.WithMessage(err, "Failed to form outgoing lookup request.") } - msg := message.Send{ - Recipient: m.udID, - Payload: requestMarshaled, - MessageType: message.UdLookup, + f := func(payload []byte, err error) { + m.lookupResponseProcess(uid, callback, payload, err) } - // Register the request in the response map so it can be processed on return - responseChan := make(chan *LookupResponse, 1) - m.inProgressLookupMux.Lock() - m.inProgressLookup[commID] = responseChan - m.inProgressLookupMux.Unlock() - - // Send the request - rounds, err := m.net.SendUnsafe(msg, params.GetDefaultUnsafe()) + err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, LookupTag, + maxLookupMessages, f, timeout) if err != nil { - return errors.WithMessage(err, "Failed to send the lookup request") + return errors.WithMessage(err, "Failed to transmit lookup request.") } - // Register the round event to capture if the round fails - roundFailChan := make(chan dataStructures.EventReturn, len(rounds)) + return nil +} - for _, round := range rounds { - // Subtract a millisecond to ensure this timeout will trigger before the - // one below - m.net.GetInstance().GetRoundEvents().AddRoundEventChan(round, - roundFailChan, timeout-1*time.Millisecond, states.FAILED, - states.COMPLETED) +func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback, + payload []byte, err error) { + if err != nil { + go callback(contact.Contact{}, errors.WithMessage(err, "Failed to lookup.")) + return } - // Start the go routine which will trigger the callback - go func() { - timer := time.NewTimer(timeout) - - var err error - var c contact.Contact - - done := false - for !done { - select { - // Return an error if the round fails - case fail := <-roundFailChan: - if states.Round(fail.RoundInfo.State)==states.FAILED || fail.TimedOut{ - fType := "" - if fail.TimedOut{ - fType = "timeout" - }else{ - fType = fmt.Sprintf("round failure: %v", fail.RoundInfo.ID) - } - err = errors.Errorf("One or more rounds (%v) failed to " + - "resolve due to: %s; search not delivered", rounds, fType) - done = true - } - - // Return an error if the timeout is reached - case <-timer.C: - err = errors.New("Response from User Discovery did not come " + - "before timeout") - done = true - - // Return the contact if one is returned - case response := <-responseChan: - if response.Error != "" { - err = errors.Errorf("User Discovery returned an error on "+ - "lookup: %s", response.Error) - } else { - pubkey := m.grp.NewIntFromBytes(response.PubKey) - c = contact.Contact{ - ID: uid, - DhPubKey: pubkey, - OwnershipProof: nil, - Facts: nil, - } - } - done = true - } - } - - // Delete the response channel from the map - m.inProgressLookupMux.Lock() - delete(m.inProgressLookup, commID) - m.inProgressLookupMux.Unlock() + // Unmarshal the message + lookupResponse := &LookupResponse{} + if err := proto.Unmarshal(payload, lookupResponse); err != nil { + jww.WARN.Printf("Dropped a lookup response from user discovery due to "+ + "failed unmarshal: %s", err) + } + if lookupResponse.Error != "" { + err = errors.Errorf("User Discovery returned an error on lookup: %s", + lookupResponse.Error) + go callback(contact.Contact{}, err) + return + } - // Call the callback last in case it is blocking - callback(c, err) - }() + fmt.Printf("pubKey: %+v\n", lookupResponse.PubKey) + c := contact.Contact{ + ID: uid, + DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey), + } - return nil + go callback(c, nil) } diff --git a/ud/lookup_test.go b/ud/lookup_test.go index b8545de0c39d7eec23d47f132a13a52a795ba83c..413b85f9d0443411977557937dfd74cee8833d63 100644 --- a/ud/lookup_test.go +++ b/ud/lookup_test.go @@ -2,43 +2,29 @@ package ud import ( "github.com/golang/protobuf/proto" - "gitlab.com/elixxir/client/globals" - "gitlab.com/elixxir/client/interfaces" + "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces/contact" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/e2e" - "gitlab.com/elixxir/crypto/fastRNG" - "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/large" "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" - "gitlab.com/xx_network/primitives/ndf" "math/rand" "reflect" + "strings" "testing" "time" ) // Happy path. func TestManager_Lookup(t *testing.T) { - isReg := uint32(1) - // Set up manager + isReg := uint32(1) m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressLookup: map[uint64]chan *LookupResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, + grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), + udContact: contact.Contact{ID: &id.UDB}, + single: &mockSingleLookup{}, + registered: &isReg, } // Generate callback function @@ -52,17 +38,7 @@ func TestManager_Lookup(t *testing.T) { err error }{c: c, err: err} } - - // Trigger lookup response chan - go func() { - time.Sleep(1 * time.Millisecond) - m.inProgressLookup[0] <- &LookupResponse{ - PubKey: []byte{5}, - Error: "", - } - }() - - uid := id.NewIdFromUInt(rand.Uint64(), id.User, t) + uid := id.NewIdFromUInt(0x500000000000000, id.User, t) // Run the lookup err := m.Lookup(uid, callback, 10*time.Millisecond) @@ -70,27 +46,6 @@ func TestManager_Lookup(t *testing.T) { t.Errorf("Lookup() returned an error: %+v", err) } - // Generate expected Send message - payload, err := proto.Marshal(&LookupSend{ - UserID: uid.Marshal(), - CommID: m.commID - 1, - }) - if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) - } - expectedMsg := message.Send{ - Recipient: m.udID, - Payload: payload, - MessageType: message.UdLookup, - } - - // Verify the message is correct - if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).msg) { - t.Errorf("Failed to send correct message."+ - "\n\texpected: %+v\n\treceived: %+v", - expectedMsg, m.net.(*testNetworkManager).msg) - } - // Verify the callback is called select { case cb := <-callbackChan: @@ -109,27 +64,13 @@ func TestManager_Lookup(t *testing.T) { case <-time.After(100 * time.Millisecond): t.Error("Callback not called.") } - - if _, exists := m.inProgressLookup[m.commID-1]; exists { - t.Error("Failed to delete LookupResponse from inProgressLookup.") - } } -// Error path: the callback returns an error. -func TestManager_Lookup_CallbackError(t *testing.T) { - isReg := uint32(1) - // Set up manager - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressLookup: map[uint64]chan *LookupResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, - } +// Happy path. +func TestManager_lookupResponseProcess(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} - // Generate callback function + uid := id.NewIdFromUInt(rand.Uint64(), id.User, t) callbackChan := make(chan struct { c contact.Contact err error @@ -140,59 +81,38 @@ func TestManager_Lookup_CallbackError(t *testing.T) { err error }{c: c, err: err} } + pubKey := []byte{5} + expectedContact := contact.Contact{ + ID: uid, + DhPubKey: m.grp.NewIntFromBytes(pubKey), + } - // Trigger lookup response chan - go func() { - time.Sleep(1 * time.Millisecond) - m.inProgressLookup[0] <- &LookupResponse{ - PubKey: nil, - Error: "Error", - } - }() - - uid := id.NewIdFromUInt(rand.Uint64(), id.User, t) - - // Run the lookup - err := m.Lookup(uid, callback, 10*time.Millisecond) + // Generate expected Send message + payload, err := proto.Marshal(&LookupResponse{PubKey: pubKey}) if err != nil { - t.Errorf("Lookup() returned an error: %+v", err) + t.Fatalf("Failed to marshal LookupSend: %+v", err) } - // Verify the callback is called + m.lookupResponseProcess(uid, callback, payload, nil) + select { - case cb := <-callbackChan: - if cb.err == nil { - t.Error("Callback did not return an expected error.") + case results := <-callbackChan: + if results.err != nil { + t.Errorf("Callback returned an error: %+v", results.err) } - - if !reflect.DeepEqual(contact.Contact{}, cb.c) { - t.Errorf("Failed to get expected Contact."+ - "\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c) + if !reflect.DeepEqual(expectedContact, results.c) { + t.Errorf("Callback returned unexpected Contact."+ + "\nexpected: %+v\nreceived: %+v", expectedContact, results.c) } - case <-time.After(100 * time.Millisecond): - t.Error("Callback not called.") - } - - if _, exists := m.inProgressLookup[m.commID-1]; exists { - t.Error("Failed to delete LookupResponse from inProgressLookup.") + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") } } -// Error path: the round event chan times out. -func TestManager_Lookup_EventChanTimeout(t *testing.T) { - isReg := uint32(1) - // Set up manager - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressLookup: map[uint64]chan *LookupResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, - } +// Happy path: error is returned on callback when passed into function. +func TestManager_lookupResponseProcess_CallbackError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} - // Generate callback function callbackChan := make(chan struct { c contact.Contact err error @@ -204,232 +124,80 @@ func TestManager_Lookup_EventChanTimeout(t *testing.T) { }{c: c, err: err} } - uid := id.NewIdFromUInt(rand.Uint64(), id.User, t) + testErr := errors.New("lookup failure") - // Run the lookup - err := m.Lookup(uid, callback, 10*time.Millisecond) - if err != nil { - t.Errorf("Lookup() returned an error: %+v", err) - } + m.lookupResponseProcess(nil, callback, []byte{}, testErr) - // Verify the callback is called select { - case cb := <-callbackChan: - if cb.err == nil { - t.Error("Callback did not return an expected error.") + case results := <-callbackChan: + if results.err == nil || !strings.Contains(results.err.Error(), testErr.Error()) { + t.Errorf("Callback failed to return error."+ + "\nexpected: %+v\nreceived: %+v", testErr, results.err) } - - if !reflect.DeepEqual(contact.Contact{}, cb.c) { - t.Errorf("Failed to get expected Contact."+ - "\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c) - } - case <-time.After(100 * time.Millisecond): - t.Error("Callback not called.") - } - - if _, exists := m.inProgressLookup[m.commID-1]; exists { - t.Error("Failed to delete LookupResponse from inProgressLookup.") + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") } } -// Happy path. -func TestManager_lookupProcess(t *testing.T) { - isReg := uint32(1) - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressLookup: map[uint64]chan *LookupResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, - } +// Error path: LookupResponse message contains an error. +func TestManager_lookupResponseProcess_MessageError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} - c := make(chan message.Receive) - quitCh := make(chan struct{}) + uid := id.NewIdFromUInt(rand.Uint64(), id.User, t) + callbackChan := make(chan struct { + c contact.Contact + err error + }) + callback := func(c contact.Contact, err error) { + callbackChan <- struct { + c contact.Contact + err error + }{c: c, err: err} + } // Generate expected Send message - payload, err := proto.Marshal(&LookupSend{ - UserID: id.NewIdFromUInt(rand.Uint64(), id.User, t).Marshal(), - CommID: m.commID, - }) + testErr := "LookupResponse error occurred" + payload, err := proto.Marshal(&LookupResponse{Error: testErr}) if err != nil { t.Fatalf("Failed to marshal LookupSend: %+v", err) } - m.inProgressLookup[m.commID] = make(chan *LookupResponse, 1) - - // Trigger response chan - go func() { - time.Sleep(1 * time.Millisecond) - c <- message.Receive{ - Payload: payload, - Encryption: message.E2E, - } - time.Sleep(1 * time.Millisecond) - quitCh <- struct{}{} - }() - - m.lookupProcess(c, quitCh) + m.lookupResponseProcess(uid, callback, payload, nil) select { - case response := <-m.inProgressLookup[m.commID]: - expectedResponse := &LookupResponse{} - if err := proto.Unmarshal(payload, expectedResponse); err != nil { - t.Fatalf("Failed to unmarshal payload: %+v", err) + case results := <-callbackChan: + if results.err == nil || !strings.Contains(results.err.Error(), testErr) { + t.Errorf("Callback failed to return error."+ + "\nexpected: %s\nreceived: %+v", testErr, results.err) } - - if !reflect.DeepEqual(expectedResponse, response) { - t.Errorf("Recieved unexpected response."+ - "\n\texpected: %+v\n\trecieved: %+v", expectedResponse, response) - } - case <-time.After(100 * time.Millisecond): - t.Error("Response not sent.") - } -} - -// Error path: dropped lookup response due to incorrect message.Receive. -func TestManager_lookupProcess_NoLookupResponse(t *testing.T) { - isReg := uint32(1) - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressLookup: map[uint64]chan *LookupResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") } - - c := make(chan message.Receive) - quitCh := make(chan struct{}) - - // Trigger response chan - go func() { - time.Sleep(1 * time.Millisecond) - c <- message.Receive{} - time.Sleep(1 * time.Millisecond) - quitCh <- struct{}{} - }() - - m.lookupProcess(c, quitCh) - - select { - case response := <-m.inProgressLookup[m.commID]: - t.Errorf("Received unexpected response: %+v", response) - case <-time.After(10 * time.Millisecond): - return - } -} - -// testNetworkManager is a test implementation of NetworkManager interface. -type testNetworkManager struct { - instance *network.Instance - msg message.Send -} - -func (t *testNetworkManager) SendE2E(m message.Send, _ params.E2E) ([]id.Round, - e2e.MessageID, error) { - rounds := []id.Round{ - id.Round(0), - id.Round(1), - id.Round(2), - } - - t.msg = m - - return rounds, e2e.MessageID{}, nil -} - -func (t *testNetworkManager) SendUnsafe(m message.Send, _ params.Unsafe) ([]id.Round, error) { - rounds := []id.Round{ - id.Round(0), - id.Round(1), - id.Round(2), - } - - t.msg = m - - return rounds, nil -} - -func (t *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) { - return 0, ephemeral.Id{}, nil } -func (t *testNetworkManager) GetInstance() *network.Instance { - return t.instance +// mockSingleLookup is used to test the lookup function, which uses the single- +// use manager. It adheres to the SingleInterface interface. +type mockSingleLookup struct { } -func (t *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { - return nil -} +func (s *mockSingleLookup) TransmitSingleUse(_ contact.Contact, payload []byte, + _ string, _ uint8, callback single.ReplyComm, _ time.Duration) error { -func (t *testNetworkManager) Follow() (stoppable.Stoppable, error) { - return nil, nil -} - -func (t *testNetworkManager) CheckGarbledMessages() {} - -func newTestNetworkManager(i interface{}) interfaces.NetworkManager { - switch i.(type) { - case *testing.T, *testing.M, *testing.B: - break - default: - globals.Log.FATAL.Panicf("initTesting is restricted to testing only."+ - "Got %T", i) - } - - commsManager := connect.NewManagerTesting(i) - instanceComms := &connect.ProtoComms{ - Manager: commsManager, + lookupMsg := &LookupSend{} + if err := proto.Unmarshal(payload, lookupMsg); err != nil { + return errors.Errorf("Failed to unmarshal LookupSend: %+v", err) } - thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), getNDF(), nil, nil, i) + lookupResponse := &LookupResponse{PubKey: lookupMsg.UserID[:1]} + msg, err := proto.Marshal(lookupResponse) if err != nil { - globals.Log.FATAL.Panicf("Failed to create new test Instance: %v", err) + return errors.Errorf("Failed to marshal LookupResponse: %+v", err) } - thisManager := &testNetworkManager{instance: thisInstance} - - return thisManager + callback(msg, nil) + return nil } -func getNDF() *ndf.NetworkDefinition { - return &ndf.NetworkDefinition{ - E2E: ndf.Group{ - Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" + - "7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" + - "DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" + - "8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" + - "023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" + - "3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" + - "6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" + - "A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" + - "37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" + - "78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" + - "015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" + - "6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" + - "847AEF49F66E43873", - Generator: "2", - }, - CMIX: ndf.Group{ - Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" + - "C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" + - "FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" + - "B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" + - "35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" + - "F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" + - "92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" + - "3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", - Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" + - "D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" + - "6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" + - "085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" + - "AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" + - "3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" + - "BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" + - "DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", - }, - } +func (s *mockSingleLookup) StartProcesses() stoppable.Stoppable { + return stoppable.NewSingle("") } diff --git a/ud/manager.go b/ud/manager.go index d0a3ba4a3209a93c1f0da8519ae24324bd6bcd6a..ad7519969b1c6c136c6c562565abcfa6038f7e22 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -5,7 +5,8 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/comms/client" @@ -14,11 +15,17 @@ import ( "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" - "sync" + "time" ) +type SingleInterface interface { + TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyComm, + time.Duration) error + StartProcesses() stoppable.Stoppable +} + type Manager struct { - //external + // External client *api.Client comms *client.Comms rng *fastRNG.StreamGenerator @@ -26,104 +33,75 @@ type Manager struct { storage *storage.Session net interfaces.NetworkManager - //loaded from external access - udID *id.ID - privKey *rsa.PrivateKey - grp *cyclic.Group - - //internal maps - host *connect.Host - inProgressLookup map[uint64]chan *LookupResponse - inProgressLookupMux sync.RWMutex - - inProgressSearch map[uint64]chan *SearchResponse - inProgressSearchMux sync.RWMutex + // Loaded from external access + udContact contact.Contact + privKey *rsa.PrivateKey + grp *cyclic.Group - //State tracking - commID uint64 - commIDLock sync.Mutex + // Internal maps + host *connect.Host + single SingleInterface 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. -func NewManager(client *api.Client) (*Manager, error) { +func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { jww.INFO.Println("ud.NewManager()") if !client.GetHealth().IsHealthy() { - return nil, errors.New("cannot start UD Manager when network " + - "was never healthy") + return nil, errors.New("cannot start UD Manager when network was " + + "never healthy.") } m := &Manager{ - client: client, - comms: client.GetComms(), - rng: client.GetRng(), - sw: client.GetSwitchboard(), - storage: client.GetStorage(), - net: client.GetNetworkInterface(), - inProgressLookup: make(map[uint64]chan *LookupResponse), - inProgressSearch: make(map[uint64]chan *SearchResponse), + client: client, + comms: client.GetComms(), + rng: client.GetRng(), + sw: client.GetSwitchboard(), + storage: client.GetStorage(), + net: client.GetNetworkInterface(), + udContact: contact.Contact{}, + 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.udID, 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 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") + return nil, errors.New("NDF does not have User Discovery information, " + + "is there network access?: Cert not present.") } - //create the user discovery host object + // 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() - if m.host, err = m.comms.AddHost(m.udID, def.UDB.Address, []byte(def.UDB.Cert), - hp); err != nil { - return nil, errors.WithMessage(err, "User Discovery host "+ - "object could not be constructed") + m.host, err = m.comms.AddHost(m.udContact.ID, def.UDB.Address, []byte(def.UDB.Cert), hp) + if err != nil { + return nil, errors.WithMessage(err, "User Discovery host object could "+ + "not be constructed.") } - //get the commonly used data from storage + // Get the commonly used data from storage m.privKey = m.storage.GetUser().ReceptionRSA - //load the last used commID - m.loadCommID() - - //load if the client is registered + // Load if the client is registered m.loadRegistered() - //store the pointer to the group locally for easy access + // Store the pointer to the group locally for easy access m.grp = m.storage.E2e().GetGroup() return m, nil } - -func (m *Manager) StartProcesses() { - m.client.AddService(m.startProcesses) -} - -func (m *Manager) startProcesses() stoppable.Stoppable { - - lookupStop := stoppable.NewSingle("UDLookup") - lookupChan := make(chan message.Receive, 100) - m.sw.RegisterChannel("UDLookupResponse", m.udID, message.UdLookupResponse, lookupChan) - go m.lookupProcess(lookupChan, lookupStop.Quit()) - - searchStop := stoppable.NewSingle("UDSearch") - searchChan := make(chan message.Receive, 100) - m.sw.RegisterChannel("UDSearchResponse", m.udID, message.UdSearchResponse, searchChan) - go m.searchProcess(searchChan, searchStop.Quit()) - - udMulti := stoppable.NewMulti("UD") - udMulti.Add(lookupStop) - udMulti.Add(searchStop) - return lookupStop -} diff --git a/ud/search.go b/ud/search.go index b3a1cd190207a389bbede70911963458d356a06e..5367fe10c5d65b22814a799f6071584ffeca8e24 100644 --- a/ud/search.go +++ b/ud/search.go @@ -1,171 +1,86 @@ package ud import ( - "fmt" "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/contact" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/crypto/factID" "gitlab.com/elixxir/primitives/fact" - "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" "time" ) -type SearchCallback func([]contact.Contact, error) - -func (m *Manager) searchProcess(c chan message.Receive, quitCh <-chan struct{}) { - for true { - select { - case <-quitCh: - return - case response := <-c: - // Unmarshal the message - searchResponse := &SearchResponse{} - if err := proto.Unmarshal(response.Payload, searchResponse); err != nil { - jww.WARN.Printf("Dropped a search response from user "+ - "discovery due to failed unmarshal: %s", err) - } +// SearchTag specifies which callback to trigger when UD receives a search +// request. +const SearchTag = "xxNetwork_UdLookup" - // Get the appropriate channel from the lookup - m.inProgressSearchMux.RLock() - ch, ok := m.inProgressSearch[searchResponse.CommID] - m.inProgressSearchMux.RUnlock() - if !ok { - jww.WARN.Printf("Dropped a search response from user "+ - "discovery due to unknown comm ID: %d", - searchResponse.CommID) - } +// TODO: reconsider where this comes from +const maxSearchMessages = 20 - // Send the response on the correct channel - // Drop if the send cannot be completed - select { - case ch <- searchResponse: - default: - jww.WARN.Printf("Dropped a search response from user "+ - "discovery due to failure to transmit to handling thread: "+ - "commID: %d", searchResponse.CommID) - } - } - } -} +type searchCallback func([]contact.Contact, error) -// Searches for the passed Facts. The SearchCallback will return -// a list of contacts, each having the facts it hit against. -// This is NOT intended to be used to search for multiple users at once, that -// can have a privacy reduction. Instead, it is intended to be used to search -// for a user where multiple pieces of information is known. -func (m *Manager) Search(list fact.FactList, callback SearchCallback, timeout time.Duration) error { +// Search searches for the passed Facts. The searchCallback will return a list +// of contacts, each having the facts it hit against. This is NOT intended to be +// used to search for multiple users at once; that can have a privacy reduction. +// Instead, it is intended to be used to search for a user where multiple pieces +// of information is known. +func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout) if !m.IsRegistered() { - return errors.New("Failed to search: " + - "client is not registered") + return errors.New("Failed to search: client is not registered.") } - // Get the ID of this comm so it can be connected to its response - commID := m.getCommID() - factHashes, factMap := hashFactList(list) - // Build the request - request := &SearchSend{ - Fact: factHashes, - CommID: commID, - } - + // Build the request and marshal it + request := &SearchSend{Fact: factHashes} requestMarshaled, err := proto.Marshal(request) if err != nil { - return errors.WithMessage(err, "Failed to form outgoing search request") + return errors.WithMessage(err, "Failed to form outgoing search request.") } - //cUID := m.client.GetUser().ID - - msg := message.Send{ - Recipient: m.udID, - Payload: requestMarshaled, - MessageType: message.UdSearch, + f := func(payload []byte, err error) { + m.searchResponseHandler(factMap, callback, payload, err) } - // Register the request in the response map so it can be processed on return - responseChan := make(chan *SearchResponse) - m.inProgressSearchMux.Lock() - m.inProgressSearch[commID] = responseChan - m.inProgressSearchMux.Unlock() - - // Send the request - rounds, err := m.net.SendUnsafe(msg, params.GetDefaultUnsafe()) + err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, SearchTag, + maxSearchMessages, f, timeout) if err != nil { - return errors.WithMessage(err, "Failed to send the search request") + return errors.WithMessage(err, "Failed to transmit search request.") } - // Register the round event to capture if the round fails - roundFailChan := make(chan dataStructures.EventReturn, len(rounds)) + return nil +} - for _, round := range rounds { - // Subtract a millisecond to ensure this timeout will trigger before the - // one below - m.net.GetInstance().GetRoundEvents().AddRoundEventChan(round, - roundFailChan, timeout-1*time.Millisecond, states.FAILED, - states.COMPLETED) +func (m *Manager) searchResponseHandler(factMap map[string]fact.Fact, + callback searchCallback, payload []byte, err error) { + if err != nil { + go callback(nil, errors.WithMessage(err, "Failed to search.")) + return } - // Start the go routine which will trigger the callback - go func() { - timer := time.NewTimer(timeout) - - var err error - var c []contact.Contact - - done := false - for !done { - select { - // Return an error if the round fails - case fail := <-roundFailChan: - if states.Round(fail.RoundInfo.State) == states.FAILED || fail.TimedOut { - fType := "" - if fail.TimedOut { - fType = "timeout" - } else { - fType = fmt.Sprintf("round failure: %v", fail.RoundInfo.ID) - } - err = errors.Errorf("One or more rounds (%v) failed to "+ - "resolve due to: %s; search not delivered", rounds, fType) - done = true - } - - // Return an error if the timeout is reached - case <-timer.C: - err = errors.New("Response from User Discovery did not come " + - "before timeout") - done = true - - // Return the contacts if one is returned - case response := <-responseChan: - if response.Error != "" { - err = errors.Errorf("User Discovery returned an error on "+ - "search: %s", response.Error) - } else { - jww.INFO.Printf("%v", response.Contacts) - c, err = m.parseContacts(response.Contacts, factMap) - } - done = true - } - } - - // Delete the response channel from the map - m.inProgressSearchMux.Lock() - delete(m.inProgressSearch, commID) - m.inProgressSearchMux.Unlock() + // Unmarshal the message + searchResponse := &SearchResponse{} + if err := proto.Unmarshal(payload, searchResponse); err != nil { + jww.WARN.Printf("Dropped a search response from user discovery due to "+ + "failed unmarshal: %s", err) + } + if searchResponse.Error != "" { + err = errors.Errorf("User Discovery returned an error on search: %s", + searchResponse.Error) + go callback(nil, err) + return + } - // Call the callback last in case it is blocking - callback(c, err) - }() + c, err := m.parseContacts(searchResponse.Contacts, factMap) + if err != nil { + go callback(nil, errors.WithMessage(err, "Failed to parse contacts from "+ + "remote server.")) + return + } - return nil + go callback(c, nil) } // hashFactList hashes each fact in the FactList into a HashFact and returns a @@ -188,7 +103,8 @@ func hashFactList(list fact.FactList) ([]*HashFact, map[string]fact.Fact) { // parseContacts parses the list of Contacts in the SearchResponse and returns a // list of contact.Contact with their ID and public key. -func (m *Manager) parseContacts(response []*Contact, hashMap map[string]fact.Fact) ([]contact.Contact, error) { +func (m *Manager) parseContacts(response []*Contact, + hashMap map[string]fact.Fact) ([]contact.Contact, error) { contacts := make([]contact.Contact, len(response)) // Convert each contact message into a new contact.Contact @@ -196,7 +112,7 @@ func (m *Manager) parseContacts(response []*Contact, hashMap map[string]fact.Fac // Unmarshal user ID bytes uid, err := id.Unmarshal(c.UserID) if err != nil { - return nil, errors.Errorf("Failed to parse Contact user ID: %+v", err) + return nil, errors.Errorf("failed to parse Contact user ID: %+v", err) } // Create new Contact diff --git a/ud/search_test.go b/ud/search_test.go index 4b4d7eba084967ea9165e0df3c1c9c7ce35af912..bbbb65c0d517f8abaa09df06628a5acecd663e27 100644 --- a/ud/search_test.go +++ b/ud/search_test.go @@ -1,33 +1,33 @@ package ud import ( + "fmt" "github.com/golang/protobuf/proto" + errors "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces/contact" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/factID" - "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/primitives/fact" - "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/large" "gitlab.com/xx_network/primitives/id" + "math/rand" "reflect" + "strings" "testing" "time" ) // Happy path. func TestManager_Search(t *testing.T) { - isReg := uint32(1) // Set up manager + isReg := uint32(1) + grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2)) m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressSearch: map[uint64]chan *SearchResponse{}, - net: newTestNetworkManager(t), + grp: grp, + udContact: contact.Contact{ID: &id.UDB, DhPubKey: grp.NewInt(42)}, + single: &mockSingleSearch{}, registered: &isReg, } @@ -44,60 +44,29 @@ func TestManager_Search(t *testing.T) { } // Generate fact list - factList := fact.FactList{ - {Fact: "fact1", T: fact.Username}, - {Fact: "fact2", T: fact.Email}, - {Fact: "fact3", T: fact.Phone}, + var factList fact.FactList + for i := 0; i < 10; i++ { + factList = append(factList, fact.Fact{ + Fact: fmt.Sprintf("fact %d", i), + T: fact.FactType(rand.Intn(4)), + }) } - - // Trigger lookup response chan - responseContacts := []*Contact{ - { - UserID: id.NewIdFromUInt(5, id.User, t).Bytes(), - PubKey: []byte{42}, - TrigFacts: []*HashFact{ - {Hash: factID.Fingerprint(factList[0]), Type: int32(factList[0].T)}, - {Hash: factID.Fingerprint(factList[1]), Type: int32(factList[1].T)}, - {Hash: factID.Fingerprint(factList[2]), Type: int32(factList[2].T)}, - }, - }, + factHashes, _ := hashFactList(factList) + + var contacts []*Contact + for i, hash := range factHashes { + contacts = append(contacts, &Contact{ + UserID: id.NewIdFromString("user", id.User, t).Marshal(), + PubKey: []byte{byte(i + 1)}, + TrigFacts: []*HashFact{hash}, + }) } - go func() { - time.Sleep(1 * time.Millisecond) - m.inProgressSearch[0] <- &SearchResponse{ - Contacts: responseContacts, - Error: "", - } - }() - // Run the search - err := m.Search(factList, callback, 20*time.Millisecond) + err := m.Search(factList, callback, 10*time.Millisecond) if err != nil { t.Errorf("Search() returned an error: %+v", err) } - // Generate expected Send message - factHashes, factMap := hashFactList(factList) - payload, err := proto.Marshal(&SearchSend{ - Fact: factHashes, - CommID: m.commID - 1, - }) - if err != nil { - t.Fatalf("Failed to marshal SearchSend: %+v", err) - } - expectedMsg := message.Send{ - Recipient: m.udID, - Payload: payload, - MessageType: message.UdSearch, - } - - // Verify the message is correct - if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).msg) { - t.Errorf("Failed to send correct message."+ - "\n\texpected: %+v\n\treceived: %+v", - expectedMsg, m.net.(*testNetworkManager).msg) - } - // Verify the callback is called select { case cb := <-callbackChan: @@ -105,38 +74,146 @@ func TestManager_Search(t *testing.T) { t.Errorf("Callback returned an error: %+v", cb.err) } - expectedContacts, err := m.parseContacts(responseContacts, factMap) - if err != nil { - t.Fatalf("parseResponseContacts() returned an error: %+v", err) - } - if !reflect.DeepEqual(expectedContacts, cb.c) { + expectedContacts := []contact.Contact{m.udContact} + if !contact.Equal(expectedContacts[0], cb.c[0]) { t.Errorf("Failed to get expected Contacts."+ - "\n\texpected: %v\n\treceived: %v", expectedContacts, cb.c) + "\n\texpected: %+v\n\treceived: %+v", expectedContacts, cb.c) } case <-time.After(100 * time.Millisecond): t.Error("Callback not called.") } - - if _, exists := m.inProgressSearch[m.commID-1]; exists { - t.Error("Failed to delete SearchResponse from inProgressSearch.") - } } -// Error path: the callback returns an error. -func TestManager_Search_CallbackError(t *testing.T) { - isReg := uint32(1) - // Set up manager - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressSearch: map[uint64]chan *SearchResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, - } +// +// // Error path: the callback returns an error. +// func TestManager_Search_CallbackError(t *testing.T) { +// isReg := uint32(1) +// // Set up manager +// m := &Manager{ +// rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), +// grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), +// storage: storage.InitTestingSession(t), +// udContact: contact.Contact{ID: &id.UDB}, +// net: newTestNetworkManager(t), +// registered: &isReg, +// } +// +// // Generate callback function +// callbackChan := make(chan struct { +// c []contact.Contact +// err error +// }) +// callback := func(c []contact.Contact, err error) { +// callbackChan <- struct { +// c []contact.Contact +// err error +// }{c: c, err: err} +// } +// +// // Generate fact list +// factList := fact.FactList{ +// {Fact: "fact1", T: fact.Username}, +// {Fact: "fact2", T: fact.Email}, +// {Fact: "fact3", T: fact.Phone}, +// } +// +// // Trigger lookup response chan +// // go func() { +// // time.Sleep(1 * time.Millisecond) +// // m.inProgressSearch[0] <- &SearchResponse{ +// // Contacts: nil, +// // Error: "Error", +// // } +// // }() +// +// // Run the search +// err := m.Search(factList, callback, 10*time.Millisecond) +// if err != nil { +// t.Errorf("Search() returned an error: %+v", err) +// } +// +// // Verify the callback is called +// select { +// case cb := <-callbackChan: +// if cb.err == nil { +// t.Error("Callback did not return an expected error.") +// } +// +// if cb.c != nil { +// t.Errorf("Failed to get expected Contacts."+ +// "\n\texpected: %v\n\treceived: %v", nil, cb.c) +// } +// case <-time.After(100 * time.Millisecond): +// t.Error("Callback not called.") +// } +// +// // if _, exists := m.inProgressSearch[m.commID-1]; exists { +// // t.Error("Failed to delete SearchResponse from inProgressSearch.") +// // } +// } +// +// // Error path: the round event chan times out. +// func TestManager_Search_EventChanTimeout(t *testing.T) { +// isReg := uint32(1) +// // Set up manager +// m := &Manager{ +// rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), +// grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), +// storage: storage.InitTestingSession(t), +// udContact: contact.Contact{ID: &id.UDB}, +// net: newTestNetworkManager(t), +// registered: &isReg, +// } +// +// // Generate callback function +// callbackChan := make(chan struct { +// c []contact.Contact +// err error +// }) +// callback := func(c []contact.Contact, err error) { +// callbackChan <- struct { +// c []contact.Contact +// err error +// }{c: c, err: err} +// } +// +// // Generate fact list +// factList := fact.FactList{ +// {Fact: "fact1", T: fact.Username}, +// {Fact: "fact2", T: fact.Email}, +// {Fact: "fact3", T: fact.Phone}, +// } +// +// // Run the search +// err := m.Search(factList, callback, 10*time.Millisecond) +// if err != nil { +// t.Errorf("Search() returned an error: %+v", err) +// } +// +// // Verify the callback is called +// select { +// case cb := <-callbackChan: +// if cb.err == nil { +// t.Error("Callback did not return an expected error.") +// } +// +// if cb.c != nil { +// t.Errorf("Failed to get expected Contacts."+ +// "\n\texpected: %v\n\treceived: %v", nil, cb.c) +// } +// case <-time.After(100 * time.Millisecond): +// t.Error("Callback not called.") +// } +// +// // if _, exists := m.inProgressSearch[m.commID-1]; exists { +// // t.Error("Failed to delete SearchResponse from inProgressSearch.") +// // } +// } + +// Happy path. +func TestManager_searchResponseHandler(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} - // Generate callback function callbackChan := make(chan struct { c []contact.Contact err error @@ -149,62 +226,121 @@ func TestManager_Search_CallbackError(t *testing.T) { } // Generate fact list - factList := fact.FactList{ - {Fact: "fact1", T: fact.Username}, - {Fact: "fact2", T: fact.Email}, - {Fact: "fact3", T: fact.Phone}, + var factList fact.FactList + for i := 0; i < 10; i++ { + factList = append(factList, fact.Fact{ + Fact: fmt.Sprintf("fact %d", i), + T: fact.FactType(rand.Intn(4)), + }) } + factHashes, factMap := hashFactList(factList) - // Trigger lookup response chan - go func() { - time.Sleep(1 * time.Millisecond) - m.inProgressSearch[0] <- &SearchResponse{ - Contacts: nil, - Error: "Error", - } - }() + var contacts []*Contact + var expectedContacts []contact.Contact + for i, hash := range factHashes { + contacts = append(contacts, &Contact{ + UserID: id.NewIdFromString("user", id.User, t).Marshal(), + PubKey: []byte{byte(i + 1)}, + TrigFacts: []*HashFact{hash}, + }) + expectedContacts = append(expectedContacts, contact.Contact{ + ID: id.NewIdFromString("user", id.User, t), + DhPubKey: m.grp.NewIntFromBytes([]byte{byte(i + 1)}), + Facts: fact.FactList{factMap[string(hash.Hash)]}, + }) + } - // Run the search - err := m.Search(factList, callback, 10*time.Millisecond) + // Generate expected Send message + payload, err := proto.Marshal(&SearchResponse{Contacts: contacts}) if err != nil { - t.Errorf("Search() returned an error: %+v", err) + t.Fatalf("Failed to marshal LookupSend: %+v", err) } - // Verify the callback is called + m.searchResponseHandler(factMap, callback, payload, nil) + select { - case cb := <-callbackChan: - if cb.err == nil { - t.Error("Callback did not return an expected error.") + case results := <-callbackChan: + if results.err != nil { + t.Errorf("Callback returned an error: %+v", results.err) } - - if cb.c != nil { - t.Errorf("Failed to get expected Contacts."+ - "\n\texpected: %v\n\treceived: %v", nil, cb.c) + if !reflect.DeepEqual(expectedContacts, results.c) { + t.Errorf("Callback returned incorrect Contacts."+ + "\nexpected: %+v\nreceived: %+v", expectedContacts, results.c) } - case <-time.After(100 * time.Millisecond): - t.Error("Callback not called.") + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") + } +} + +// Happy path: error is returned on callback when passed into function. +func TestManager_searchResponseHandler_CallbackError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} + + callbackChan := make(chan struct { + c []contact.Contact + err error + }) + callback := func(c []contact.Contact, err error) { + callbackChan <- struct { + c []contact.Contact + err error + }{c: c, err: err} } - if _, exists := m.inProgressSearch[m.commID-1]; exists { - t.Error("Failed to delete SearchResponse from inProgressSearch.") + testErr := errors.New("search failure") + + m.searchResponseHandler(map[string]fact.Fact{}, callback, []byte{}, testErr) + + select { + case results := <-callbackChan: + if results.err == nil || !strings.Contains(results.err.Error(), testErr.Error()) { + t.Errorf("Callback failed to return error."+ + "\nexpected: %+v\nreceived: %+v", testErr, results.err) + } + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") } } -// Error path: the round event chan times out. -func TestManager_Search_EventChanTimeout(t *testing.T) { - isReg := uint32(1) - // Set up manager - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressSearch: map[uint64]chan *SearchResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, +// Error path: SearchResponse message contains an error. +func TestManager_searchResponseHandler_MessageError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} + + callbackChan := make(chan struct { + c []contact.Contact + err error + }) + callback := func(c []contact.Contact, err error) { + callbackChan <- struct { + c []contact.Contact + err error + }{c: c, err: err} } - // Generate callback function + // Generate expected Send message + testErr := "SearchResponse error occurred" + payload, err := proto.Marshal(&SearchResponse{Error: testErr}) + if err != nil { + t.Fatalf("Failed to marshal LookupSend: %+v", err) + } + + m.searchResponseHandler(map[string]fact.Fact{}, callback, payload, nil) + + select { + case results := <-callbackChan: + if results.err == nil || !strings.Contains(results.err.Error(), testErr) { + t.Errorf("Callback failed to return error."+ + "\nexpected: %s\nreceived: %+v", testErr, results.err) + } + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") + } +} + +// Error path: contact is malformed and cannot be parsed. +func TestManager_searchResponseHandler_ParseContactError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} + callbackChan := make(chan struct { c []contact.Contact err error @@ -216,128 +352,141 @@ func TestManager_Search_EventChanTimeout(t *testing.T) { }{c: c, err: err} } - // Generate fact list - factList := fact.FactList{ - {Fact: "fact1", T: fact.Username}, - {Fact: "fact2", T: fact.Email}, - {Fact: "fact3", T: fact.Phone}, + var contacts []*Contact + for i := 0; i < 10; i++ { + contacts = append(contacts, &Contact{ + UserID: []byte{byte(i + 1)}, + }) } - // Run the search - err := m.Search(factList, callback, 10*time.Millisecond) + // Generate expected Send message + payload, err := proto.Marshal(&SearchResponse{Contacts: contacts}) if err != nil { - t.Errorf("Search() returned an error: %+v", err) + t.Fatalf("Failed to marshal LookupSend: %+v", err) } - // Verify the callback is called + m.searchResponseHandler(nil, callback, payload, nil) + select { - case cb := <-callbackChan: - if cb.err == nil { - t.Error("Callback did not return an expected error.") + case results := <-callbackChan: + if results.err == nil || !strings.Contains(results.err.Error(), "failed to parse Contact user ID") { + t.Errorf("Callback failed to return error: %+v", results.err) } + case <-time.NewTimer(50 * time.Millisecond).C: + t.Error("Callback time out.") + } +} - if cb.c != nil { - t.Errorf("Failed to get expected Contacts."+ - "\n\texpected: %v\n\treceived: %v", nil, cb.c) +// Happy path. +func Test_hashFactList(t *testing.T) { + var factList fact.FactList + var expectedHashFacts []*HashFact + expectedHashMap := make(map[string]fact.Fact) + for i := 0; i < 10; i++ { + f := fact.Fact{ + Fact: fmt.Sprintf("fact %d", i), + T: fact.FactType(rand.Intn(4)), } - case <-time.After(100 * time.Millisecond): - t.Error("Callback not called.") + factList = append(factList, f) + expectedHashFacts = append(expectedHashFacts, &HashFact{ + Hash: factID.Fingerprint(f), + Type: int32(f.T), + }) + expectedHashMap[string(factID.Fingerprint(f))] = f + } + + hashFacts, hashMap := hashFactList(factList) + + if !reflect.DeepEqual(expectedHashFacts, hashFacts) { + t.Errorf("hashFactList() failed to return the expected hash facts."+ + "\nexpected: %+v\nreceived: %+v", expectedHashFacts, hashFacts) } - if _, exists := m.inProgressSearch[m.commID-1]; exists { - t.Error("Failed to delete SearchResponse from inProgressSearch.") + if !reflect.DeepEqual(expectedHashMap, hashMap) { + t.Errorf("hashFactList() failed to return the expected hash map."+ + "\nexpected: %+v\nreceived: %+v", expectedHashMap, hashMap) } } // Happy path. -func TestManager_searchProcess(t *testing.T) { - isReg := uint32(1) - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressSearch: map[uint64]chan *SearchResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, +func TestManager_parseContacts(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} + + // Generate fact list + var factList fact.FactList + for i := 0; i < 10; i++ { + factList = append(factList, fact.Fact{ + Fact: fmt.Sprintf("fact %d", i), + T: fact.FactType(rand.Intn(4)), + }) } + factHashes, factMap := hashFactList(factList) - c := make(chan message.Receive) - quitCh := make(chan struct{}) + var contacts []*Contact + var expectedContacts []contact.Contact + for i, hash := range factHashes { + contacts = append(contacts, &Contact{ + UserID: id.NewIdFromString("user", id.User, t).Marshal(), + PubKey: []byte{byte(i + 1)}, + TrigFacts: []*HashFact{hash}, + }) + expectedContacts = append(expectedContacts, contact.Contact{ + ID: id.NewIdFromString("user", id.User, t), + DhPubKey: m.grp.NewIntFromBytes([]byte{byte(i + 1)}), + Facts: fact.FactList{factMap[string(hash.Hash)]}, + }) + } - // Generate expected Send message - payload, err := proto.Marshal(&SearchSend{ - Fact: []*HashFact{&HashFact{ - Hash: []byte{1}, - Type: 0, - }}, - CommID: m.commID, - }) + testContacts, err := m.parseContacts(contacts, factMap) if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) + t.Errorf("parseContacts() returned an error: %+v", err) } - m.inProgressSearch[m.commID] = make(chan *SearchResponse, 1) - - // Trigger response chan - go func() { - time.Sleep(1 * time.Millisecond) - c <- message.Receive{ - Payload: payload, - Encryption: message.E2E, - } - time.Sleep(1 * time.Millisecond) - quitCh <- struct{}{} - }() - - m.searchProcess(c, quitCh) - - select { - case response := <-m.inProgressSearch[m.commID]: - expectedResponse := &SearchResponse{} - if err := proto.Unmarshal(payload, expectedResponse); err != nil { - t.Fatalf("Failed to unmarshal payload: %+v", err) - } - - if !reflect.DeepEqual(expectedResponse, response) { - t.Errorf("Recieved unexpected response."+ - "\n\texpected: %+v\n\trecieved: %+v", expectedResponse, response) - } - case <-time.After(100 * time.Millisecond): - t.Error("Response not sent.") + if !reflect.DeepEqual(expectedContacts, testContacts) { + t.Errorf("parseContacts() did not return the expected contacts."+ + "\nexpected: %+v\nreceived: %+v", expectedContacts, testContacts) } } -// Error path: dropped lookup response due to incorrect message.Receive. -func TestManager_searchpProcess_NoSearchResponse(t *testing.T) { - isReg := uint32(1) - m := &Manager{ - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - storage: storage.InitTestingSession(t), - udID: &id.UDB, - inProgressSearch: map[uint64]chan *SearchResponse{}, - net: newTestNetworkManager(t), - registered: &isReg, +// Error path: provided contact IDs are malformed and cannot be unmarshaled. +func TestManager_parseContacts_IdUnmarshalError(t *testing.T) { + m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} + contacts := []*Contact{{UserID: []byte("invalid ID")}} + + _, err := m.parseContacts(contacts, nil) + if err == nil || !strings.Contains(err.Error(), "failed to parse Contact user ID") { + t.Errorf("parseContacts() did not return an error when IDs are invalid: %+v", err) } +} - c := make(chan message.Receive) - quitCh := make(chan struct{}) +// mockSingleSearch is used to test the search function, which uses the single- +// use manager. It adheres to the SingleInterface interface. +type mockSingleSearch struct { +} - // Trigger response chan - go func() { - time.Sleep(1 * time.Millisecond) - c <- message.Receive{} - time.Sleep(1 * time.Millisecond) - quitCh <- struct{}{} - }() +func (s *mockSingleSearch) TransmitSingleUse(partner contact.Contact, payload []byte, + _ string, _ uint8, callback single.ReplyComm, _ time.Duration) error { - m.lookupProcess(c, quitCh) + searchMsg := &SearchSend{} + if err := proto.Unmarshal(payload, searchMsg); err != nil { + return errors.Errorf("Failed to unmarshal SearchSend: %+v", err) + } - select { - case response := <-m.inProgressSearch[m.commID]: - t.Errorf("Received unexpected response: %+v", response) - case <-time.After(10 * time.Millisecond): - return + searchResponse := &SearchResponse{ + Contacts: []*Contact{{ + UserID: partner.ID.Marshal(), + PubKey: partner.DhPubKey.Bytes(), + }}, + } + msg, err := proto.Marshal(searchResponse) + if err != nil { + return errors.Errorf("Failed to marshal SearchResponse: %+v", err) } -} \ No newline at end of file + + callback(msg, nil) + return nil +} + +func (s *mockSingleSearch) StartProcesses() stoppable.Stoppable { + return stoppable.NewSingle("") +} diff --git a/ud/udMessages.pb.go b/ud/udMessages.pb.go index 33035584df24e07f83b22349442cce568d176f80..1a245ab0c669f283d9ac5ee522d6e3f763501c4e 100644 --- a/ud/udMessages.pb.go +++ b/ud/udMessages.pb.go @@ -128,12 +128,10 @@ func (m *Contact) GetTrigFacts() []*HashFact { // Message sent to UDB to search for users type SearchSend struct { // PublicKey used in the registration - Fact []*HashFact `protobuf:"bytes,1,rep,name=fact,proto3" json:"fact,omitempty"` - // ID of the session used to create this session - CommID uint64 `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Fact []*HashFact `protobuf:"bytes,1,rep,name=fact,proto3" json:"fact,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *SearchSend) Reset() { *m = SearchSend{} } @@ -168,18 +166,10 @@ func (m *SearchSend) GetFact() []*HashFact { return nil } -func (m *SearchSend) GetCommID() uint64 { - if m != nil { - return m.CommID - } - return 0 -} - // Message sent from UDB to client in response to a search type SearchResponse struct { // ID of the session created Contacts []*Contact `protobuf:"bytes,1,rep,name=contacts,proto3" json:"contacts,omitempty"` - CommID uint64 `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"` Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -218,13 +208,6 @@ func (m *SearchResponse) GetContacts() []*Contact { return nil } -func (m *SearchResponse) GetCommID() uint64 { - if m != nil { - return m.CommID - } - return 0 -} - func (m *SearchResponse) GetError() string { if m != nil { return m.Error @@ -235,7 +218,6 @@ func (m *SearchResponse) GetError() string { // Message sent to UDB for looking up a user type LookupSend struct { UserID []byte `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"` - CommID uint64 `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -273,17 +255,9 @@ func (m *LookupSend) GetUserID() []byte { return nil } -func (m *LookupSend) GetCommID() uint64 { - if m != nil { - return m.CommID - } - return 0 -} - // Message sent from UDB for looking up a user type LookupResponse struct { PubKey []byte `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"` - CommID uint64 `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"` Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -322,13 +296,6 @@ func (m *LookupResponse) GetPubKey() []byte { return nil } -func (m *LookupResponse) GetCommID() uint64 { - if m != nil { - return m.CommID - } - return 0 -} - func (m *LookupResponse) GetError() string { if m != nil { return m.Error @@ -345,28 +312,25 @@ func init() { proto.RegisterType((*LookupResponse)(nil), "parse.LookupResponse") } -func init() { - proto.RegisterFile("udMessages.proto", fileDescriptor_9e0cfdc16fb09bb6) -} +func init() { proto.RegisterFile("udMessages.proto", fileDescriptor_9e0cfdc16fb09bb6) } var fileDescriptor_9e0cfdc16fb09bb6 = []byte{ - // 285 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x51, 0xc1, 0x4a, 0xc3, 0x40, - 0x10, 0x65, 0x9b, 0xa4, 0xb6, 0x63, 0x89, 0xb2, 0x88, 0xe4, 0x18, 0xe2, 0x25, 0x08, 0xe6, 0x50, - 0xaf, 0x9e, 0xb4, 0x88, 0x41, 0xbd, 0x6c, 0xc1, 0x83, 0xb7, 0x6d, 0x32, 0x36, 0x2a, 0xcd, 0x2e, - 0x3b, 0x9b, 0x43, 0xff, 0x5e, 0xb2, 0x59, 0x5b, 0x84, 0x2a, 0x78, 0x9b, 0x37, 0xb3, 0xef, 0xcd, - 0x9b, 0xb7, 0x70, 0xda, 0xd5, 0xcf, 0x48, 0x24, 0xd7, 0x48, 0x85, 0x36, 0xca, 0x2a, 0x1e, 0x69, - 0x69, 0x08, 0xb3, 0x39, 0x4c, 0x1e, 0x24, 0x35, 0xf7, 0xb2, 0xb2, 0x9c, 0x43, 0xd8, 0x48, 0x6a, - 0x12, 0x96, 0xb2, 0x7c, 0x26, 0x5c, 0xdd, 0xf7, 0xec, 0x56, 0x63, 0x32, 0x4a, 0x59, 0x1e, 0x09, - 0x57, 0x67, 0x0d, 0x1c, 0xdd, 0xa9, 0xd6, 0xf6, 0x94, 0x73, 0x18, 0x77, 0x84, 0xa6, 0x5c, 0x78, - 0x92, 0x47, 0x7d, 0x5f, 0x77, 0xab, 0x47, 0xdc, 0x3a, 0xe2, 0x4c, 0x78, 0xc4, 0xaf, 0x60, 0x6a, - 0xcd, 0xfb, 0xba, 0x5f, 0x47, 0x49, 0x90, 0x06, 0xf9, 0xf1, 0xfc, 0xa4, 0x70, 0x4e, 0x8a, 0x6f, - 0x1b, 0x62, 0xff, 0x22, 0x2b, 0x01, 0x96, 0x28, 0x4d, 0xd5, 0x2c, 0xb1, 0xad, 0xf9, 0x05, 0x84, - 0x6f, 0xb2, 0xb2, 0x09, 0x3b, 0xcc, 0x73, 0xc3, 0x7e, 0x73, 0xa5, 0x36, 0x9b, 0x72, 0xe1, 0x36, - 0x87, 0xc2, 0xa3, 0xec, 0x03, 0xe2, 0x41, 0x4a, 0x20, 0x69, 0xd5, 0x12, 0xf2, 0x4b, 0x98, 0x54, - 0xc3, 0x19, 0xe4, 0x25, 0x63, 0x2f, 0xe9, 0xaf, 0x13, 0xbb, 0xf9, 0x6f, 0xaa, 0xfc, 0x0c, 0x22, - 0x34, 0x46, 0x99, 0x24, 0x48, 0x59, 0x3e, 0x15, 0x03, 0xc8, 0x6e, 0x00, 0x9e, 0x94, 0xfa, 0xec, - 0xb4, 0xb3, 0xfd, 0x47, 0x46, 0x07, 0x9d, 0xbe, 0x40, 0x3c, 0xb0, 0x77, 0x4e, 0xf7, 0x69, 0xb2, - 0x1f, 0x69, 0xfe, 0xcb, 0xd5, 0x6d, 0xf8, 0x3a, 0xea, 0xea, 0xd5, 0xd8, 0x7d, 0xff, 0xf5, 0x57, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x49, 0xa1, 0x5e, 0x12, 0x02, 0x00, 0x00, + // 266 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4b, 0xc3, 0x30, + 0x14, 0xc6, 0xc9, 0xda, 0xce, 0xed, 0x39, 0xaa, 0x04, 0x91, 0x1e, 0x4b, 0xf4, 0x50, 0x04, 0x0b, + 0xce, 0xbb, 0x07, 0x15, 0x51, 0xd4, 0x4b, 0x76, 0xf3, 0x96, 0xb5, 0xcf, 0x55, 0x84, 0x26, 0xe4, + 0x25, 0x87, 0xfd, 0xf7, 0xd2, 0x34, 0x6e, 0x08, 0xf3, 0x96, 0xef, 0xbd, 0xf7, 0xe3, 0xfb, 0xde, + 0x0b, 0x9c, 0xfa, 0xf6, 0x1d, 0x89, 0xd4, 0x06, 0xa9, 0x36, 0x56, 0x3b, 0xcd, 0x33, 0xa3, 0x2c, + 0xa1, 0x58, 0xc2, 0xec, 0x59, 0x51, 0xf7, 0xa4, 0x1a, 0xc7, 0x39, 0xa4, 0x9d, 0xa2, 0xae, 0x60, + 0x25, 0xab, 0x16, 0x32, 0xbc, 0x87, 0x9a, 0xdb, 0x1a, 0x2c, 0x26, 0x25, 0xab, 0x32, 0x19, 0xde, + 0xa2, 0x83, 0xa3, 0x07, 0xdd, 0xbb, 0x01, 0x39, 0x87, 0xa9, 0x27, 0xb4, 0x2f, 0x8f, 0x11, 0x8a, + 0x6a, 0xa8, 0x1b, 0xbf, 0x7e, 0xc5, 0x6d, 0x00, 0x17, 0x32, 0x2a, 0x7e, 0x0d, 0x73, 0x67, 0xbf, + 0x36, 0x83, 0x1d, 0x15, 0x49, 0x99, 0x54, 0xc7, 0xcb, 0x93, 0x3a, 0x24, 0xa9, 0x7f, 0x63, 0xc8, + 0xfd, 0x84, 0xb8, 0x01, 0x58, 0xa1, 0xb2, 0x4d, 0xb7, 0xc2, 0xbe, 0xe5, 0x17, 0x90, 0x7e, 0xaa, + 0xc6, 0x15, 0xec, 0x30, 0x17, 0x9a, 0x42, 0x42, 0x3e, 0x22, 0x12, 0xc9, 0xe8, 0x9e, 0x90, 0x5f, + 0xc1, 0xac, 0x19, 0xe3, 0x52, 0x44, 0xf3, 0x88, 0xc6, 0x2d, 0xe4, 0xae, 0xcf, 0xcf, 0x20, 0x43, + 0x6b, 0xb5, 0x2d, 0x92, 0x92, 0x55, 0x73, 0x39, 0x0a, 0x71, 0x09, 0xf0, 0xa6, 0xf5, 0xb7, 0x37, + 0x21, 0xc6, 0x3f, 0x3b, 0x8b, 0x3b, 0xc8, 0xc7, 0xa9, 0x9d, 0xf3, 0xfe, 0x0a, 0xec, 0xcf, 0x15, + 0x0e, 0xba, 0xdc, 0xa7, 0x1f, 0x13, 0xdf, 0xae, 0xa7, 0xe1, 0x7b, 0x6e, 0x7f, 0x02, 0x00, 0x00, + 0xff, 0xff, 0xb4, 0xff, 0x7b, 0xf5, 0xb2, 0x01, 0x00, 0x00, } diff --git a/ud/udMessages.proto b/ud/udMessages.proto index fba57dcf6c35db303cb24e477042304d895e482b..0fdc127500d548390c5a8385b445b0ab31130f90 100644 --- a/ud/udMessages.proto +++ b/ud/udMessages.proto @@ -30,27 +30,22 @@ message Contact { message SearchSend { // PublicKey used in the registration repeated HashFact fact = 1; - // ID of the session used to create this session - uint64 commID = 2; } // Message sent from UDB to client in response to a search message SearchResponse { // ID of the session created repeated Contact contacts = 1; - uint64 commID = 2; string error = 3; } // Message sent to UDB for looking up a user message LookupSend { bytes userID = 1; - uint64 commID = 2; } // Message sent from UDB for looking up a user message LookupResponse { bytes pubKey = 1; - uint64 commID = 2; string error = 3; } \ No newline at end of file