diff --git a/go.mod b/go.mod index 84ed766a410405b2465dc08a5aac8068b2034814..1a876c763bbf43a2ef81a7cecfc7aa9fb032025c 100644 --- a/go.mod +++ b/go.mod @@ -18,13 +18,13 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.3 - gitlab.com/elixxir/crypto v0.0.5-0.20201109234712-7e64de16970d + gitlab.com/elixxir/comms v0.0.4-0.20201116233755-b476dea10095 + gitlab.com/elixxir/crypto v0.0.5-0.20201118204646-9b23991834c6 gitlab.com/elixxir/ekv v0.1.3 gitlab.com/elixxir/primitives v0.0.3-0.20201116174806-97f190989704 - gitlab.com/xx_network/comms v0.0.3 + gitlab.com/xx_network/comms v0.0.4-0.20201118225304-345dad24bb1e gitlab.com/xx_network/crypto v0.0.4 - gitlab.com/xx_network/primitives v0.0.2 + gitlab.com/xx_network/primitives v0.0.3-0.20201116234927-44e42fc91e7c golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 google.golang.org/protobuf v1.25.0 gopkg.in/ini.v1 v1.61.0 // indirect diff --git a/go.sum b/go.sum index 5e5de048d2a894d2bb57d6dcccd3d2cbc5501294..afb0e6e6c207fafa138923b071915ff264c16160 100644 --- a/go.sum +++ b/go.sum @@ -252,16 +252,15 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0= github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/comms v0.0.3 h1:7cFvBZddX/8JSY5MvfPpg21niV88IpeqQkoKs15erZM= -gitlab.com/elixxir/comms v0.0.3/go.mod h1:5p7oz4yFrK037rPap6ooaWrloJrzuVZ4jnzOdvgyqnU= +gitlab.com/elixxir/comms v0.0.4-0.20201116233755-b476dea10095 h1:YptJAYLxqy5CAJIcM9kOwfxmJ2D2A8uXWTT8rgXYG+E= +gitlab.com/elixxir/comms v0.0.4-0.20201116233755-b476dea10095/go.mod h1:spFKl7jsMy8M6NDvhJ27IJ+CnZ/07JHJCYpYsG8JQ4o= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4 h1:28ftZDeYEko7xptCZzeFWS1Iam95dj46TWFVVlKmw6A= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3 h1:znCt/x2bL4y8czTPaaFkwzdgSgW3BJc/1+dxyf1jqVw= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.4 h1:8eWjvUepCU2PiqZM2NFYo6rFg1w8KWO1hMDwMNFEqoI= -gitlab.com/elixxir/crypto v0.0.4/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.5-0.20201109234712-7e64de16970d h1:9Peb/peftTVeO5gYqi37sZycMEiu05+2VZ/j8d5lldI= -gitlab.com/elixxir/crypto v0.0.5-0.20201109234712-7e64de16970d/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= +gitlab.com/elixxir/crypto v0.0.5-0.20201110193609-6b5e881867b4/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= +gitlab.com/elixxir/crypto v0.0.5-0.20201118204646-9b23991834c6 h1:HEJHC6gyVMdCZ1PSJkFDScHnsrWAMF+PFxyL2zpNrgU= +gitlab.com/elixxir/crypto v0.0.5-0.20201118204646-9b23991834c6/go.mod h1:BqvmtLM4eW+3NNOVK7U3COnnxqhJZxdCv4yziCuYhlA= gitlab.com/elixxir/ekv v0.1.3 h1:OE+LBMIhjGUMwc6hHJzYvEPNJQV7t1vMnJyIgxUMUo8= gitlab.com/elixxir/ekv v0.1.3/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= @@ -269,13 +268,12 @@ gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0Vel gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.1 h1:q61anawANlNAExfkeQEE1NCsNih6vNV1FFLoUQX6txQ= gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.2 h1:PvyOOp/A6tCtmU7YnGhCCPRdmEogEzCi0Li/WfiVjGo= -gitlab.com/elixxir/primitives v0.0.2/go.mod h1:3fxFHSlQhkV4vs+S0dZEz3Om3m+40WX8L806yvSnNFc= gitlab.com/elixxir/primitives v0.0.3-0.20201116174806-97f190989704 h1:JkFREumz8skDqkCjjzZnlf5tg+PBiMB9kfVn9z0VEfE= gitlab.com/elixxir/primitives v0.0.3-0.20201116174806-97f190989704/go.mod h1:3fxFHSlQhkV4vs+S0dZEz3Om3m+40WX8L806yvSnNFc= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.3 h1:ch1eJI4WXUE/Kz0Kp9uDWX16B+hfVFmdHY+EOai4Wzc= -gitlab.com/xx_network/comms v0.0.3/go.mod h1:YViGbRj7FjJYoaO4NpALGEd9dK/l8uUT000FEBbUTL8= +gitlab.com/xx_network/comms v0.0.4-0.20201110022115-4a6171cad07d/go.mod h1:YViGbRj7FjJYoaO4NpALGEd9dK/l8uUT000FEBbUTL8= +gitlab.com/xx_network/comms v0.0.4-0.20201118225304-345dad24bb1e h1:sJjDnRQmAmojq64OS3ZIvhDgaY1nTRYAo/3GeR+yyCI= +gitlab.com/xx_network/comms v0.0.4-0.20201118225304-345dad24bb1e/go.mod h1:YViGbRj7FjJYoaO4NpALGEd9dK/l8uUT000FEBbUTL8= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4 h1:lpKOL5mTJ2awWMfgBy30oD/UvJVrWZzUimSHlOdZZxo= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= @@ -284,6 +282,8 @@ gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da h1:CCVslUwNC gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2 h1:r45yKenJ9e7PylI1ZXJ1Es09oYNaYXjxVy9+uYlwo7Y= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= +gitlab.com/xx_network/primitives v0.0.3-0.20201116234927-44e42fc91e7c h1:mYId667WIN97E6KhPw4HDYyCjWzsG7gCM/HLTNTCXZQ= +gitlab.com/xx_network/primitives v0.0.3-0.20201116234927-44e42fc91e7c/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= 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/ud/lookup.go b/ud/lookup.go index c75c52e83f7e55c55207a32429736eba6564ecfd..9730bedd0068b88b3915429c98283903c089490c 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -35,9 +35,9 @@ func (m *Manager) lookupProcess(c chan message.Receive, quitCh <-chan struct{}) } // Get the appropriate channel from the lookup - m.inProgressMux.RLock() + m.inProgressLookupMux.RLock() ch, ok := m.inProgressLookup[lookupResponse.CommID] - m.inProgressMux.RUnlock() + m.inProgressLookupMux.RUnlock() if !ok { jww.WARN.Printf("Dropped a lookup response from user "+ "discovery due to unknown comm ID: %d", @@ -83,9 +83,9 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat // Register the request in the response map so it can be processed on return responseChan := make(chan *LookupResponse, 1) - m.inProgressMux.Lock() + m.inProgressLookupMux.Lock() m.inProgressLookup[commID] = responseChan - m.inProgressMux.Unlock() + m.inProgressLookupMux.Unlock() // Send the request rounds, _, err := m.net.SendE2E(msg, params.GetDefaultE2E()) @@ -113,7 +113,7 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat select { // Return an error if the round fails case <-roundFailChan: - err = errors.New("One or more rounds failed to resolved; " + + err = errors.New("One or more rounds failed to resolve; " + "lookup not delivered") // Return an error if the timeout is reached @@ -138,9 +138,9 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat } // Delete the response channel from the map - m.inProgressMux.Lock() + m.inProgressLookupMux.Lock() delete(m.inProgressLookup, commID) - m.inProgressMux.Unlock() + m.inProgressLookupMux.Unlock() // Call the callback last in case it is blocking callback(c, err) diff --git a/ud/lookup_test.go b/ud/lookup_test.go index 6b18671f7c862f3796834270c80b65b9ac3d630b..791e500468e57f4a3424e0ea8d0646ba45742408 100644 --- a/ud/lookup_test.go +++ b/ud/lookup_test.go @@ -28,11 +28,11 @@ import ( // Happy path. func TestManager_Lookup(t *testing.T) { // Set up manager - m := 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.NewIdFromUInt(rand.Uint64(), id.User, t), + udID: &id.UDB, inProgressLookup: map[uint64]chan *LookupResponse{}, net: newTestNetworkManager(t), } @@ -111,14 +111,14 @@ func TestManager_Lookup(t *testing.T) { } } -// Error path: the LookupResponse returns an error. -func TestManager_Lookup_LookupResponseError(t *testing.T) { +// Error path: the callback returns an error. +func TestManager_Lookup_CallbackError(t *testing.T) { // Set up manager - m := 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.NewIdFromUInt(rand.Uint64(), id.User, t), + udID: &id.UDB, inProgressLookup: map[uint64]chan *LookupResponse{}, net: newTestNetworkManager(t), } @@ -175,11 +175,11 @@ func TestManager_Lookup_LookupResponseError(t *testing.T) { // Error path: the round event chan times out. func TestManager_Lookup_EventChanTimeout(t *testing.T) { // Set up manager - m := 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.NewIdFromUInt(rand.Uint64(), id.User, t), + udID: &id.UDB, inProgressLookup: map[uint64]chan *LookupResponse{}, net: newTestNetworkManager(t), } @@ -226,11 +226,11 @@ func TestManager_Lookup_EventChanTimeout(t *testing.T) { // Happy path. func TestManager_lookupProcess(t *testing.T) { - m := 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.NewIdFromUInt(rand.Uint64(), id.User, t), + udID: &id.UDB, inProgressLookup: map[uint64]chan *LookupResponse{}, net: newTestNetworkManager(t), } @@ -280,11 +280,11 @@ func TestManager_lookupProcess(t *testing.T) { // Error path: dropped lookup response due to incorrect message.Receive. func TestManager_lookupProcess_NoLookupResponse(t *testing.T) { - m := 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.NewIdFromUInt(rand.Uint64(), id.User, t), + udID: &id.UDB, inProgressLookup: map[uint64]chan *LookupResponse{}, net: newTestNetworkManager(t), } diff --git a/ud/manager.go b/ud/manager.go index 10dcc2a8131fb64b6ad4bcc5dbc76922cf6f30cd..fac9e1eb85a4350d6e5445d464b541cbd0dda503 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -25,8 +25,11 @@ type Manager struct { udID *id.ID - inProgressLookup map[uint64]chan *LookupResponse - inProgressMux sync.RWMutex + inProgressLookup map[uint64]chan *LookupResponse + inProgressLookupMux sync.RWMutex + + inProgressSearch map[uint64]chan *SearchResponse + inProgressSearchMux sync.Mutex net interfaces.NetworkManager diff --git a/ud/remove.go b/ud/remove.go index 6d7994d662143f06281a11228fb00cf85cbba207..ae9e555147571ddae87cbccf816f877226817178 100644 --- a/ud/remove.go +++ b/ud/remove.go @@ -4,9 +4,9 @@ import ( "crypto/rand" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/any" - "gitlab.com/elixxir/client/interfaces/contact" "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/hash" + "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" "gitlab.com/xx_network/crypto/signature/rsa" @@ -16,11 +16,11 @@ type removeFactComms interface { SendDeleteMessage(host *connect.Host, message *messages.AuthenticatedMessage) (*messages.Ack, error) } -func (m *Manager) RemoveFact(fact contact.Fact) error { +func (m *Manager) RemoveFact(fact fact.Fact) error { return m.removeFact(fact, nil) } -func (m *Manager) removeFact(fact contact.Fact, rFC removeFactComms) error { +func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error { // Construct the message to send // Convert our Fact to a mixmessages Fact for sending mmFact := mixmessages.Fact{ diff --git a/ud/remove_test.go b/ud/remove_test.go index 7ec08c4e28542cb036e2e9c78afe6e5d39314b39..68ff8fd0b3910d0c53e05fcab4b5462a048623fe 100644 --- a/ud/remove_test.go +++ b/ud/remove_test.go @@ -1,9 +1,9 @@ package ud import ( - "gitlab.com/elixxir/client/interfaces/contact" "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/comms/messages" "gitlab.com/xx_network/crypto/csprng" @@ -60,7 +60,7 @@ func TestRemoveFact(t *testing.T) { privKey: cpk, } - f := contact.Fact{ + f := fact.Fact{ Fact: "testing", T: 2, } diff --git a/ud/search.go b/ud/search.go new file mode 100644 index 0000000000000000000000000000000000000000..6dc632aa059d709f19ca7ae8666ad1f270d42613 --- /dev/null +++ b/ud/search.go @@ -0,0 +1,152 @@ +package ud + +import ( + "github.com/golang/protobuf/proto" + "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/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) + +// Search... +func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error { + // 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, + } + + requestMarshaled, err := proto.Marshal(request) + if err != nil { + return errors.WithMessage(err, "Failed to form outgoing search request") + } + + msg := message.Send{ + Recipient: m.udID, + Payload: requestMarshaled, + MessageType: message.UdSearch, + } + + // 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.SendE2E(msg, params.GetDefaultE2E()) + if err != nil { + return errors.WithMessage(err, "Failed to send the search request") + } + + // Register the round event to capture if the round fails + roundFailChan := make(chan dataStructures.EventReturn, len(rounds)) + + 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) + } + + // Start the go routine which will trigger the callback + go func() { + timer := time.NewTimer(timeout) + + var err error + var c []contact.Contact + + select { + // Return an error if the round fails + case <-roundFailChan: + err = errors.New("One or more rounds failed to resolve; " + + "search not delivered") + + // Return an error if the timeout is reached + case <-timer.C: + err = errors.New("Response from User Discovery did not come " + + "before timeout") + + // 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 { + c, err = m.parseContacts(response.Contacts, factMap) + } + } + + // Delete the response channel from the map + m.inProgressSearchMux.Lock() + delete(m.inProgressSearch, commID) + m.inProgressSearchMux.Unlock() + + // Call the callback last in case it is blocking + callback(c, err) + }() + + return nil +} + +// hashFactList hashes each fact in the FactList into a HashFact and returns a +// slice of the HashFacts. Also returns a map of Facts keyed on fact hashes to +// be used for the callback return. +func hashFactList(list fact.FactList) ([]*HashFact, map[string]fact.Fact) { + hashes := make([]*HashFact, len(list)) + hashMap := make(map[string]fact.Fact, len(list)) + + for i, f := range list { + hashes[i] = &HashFact{ + Hash: factID.Fingerprint(f), + Type: int32(f.T), + } + hashMap[string(factID.Fingerprint(f))] = f + } + + return hashes, hashMap +} + +// 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) { + contacts := make([]contact.Contact, len(response)) + + // Convert each contact message into a new contact.Contact + for i, c := range response { + // 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) + } + + // Create new Contact + contacts[i] = contact.Contact{ + ID: uid, + DhPubKey: m.grp.NewIntFromBytes(c.PubKey), + Facts: []fact.Fact{}, + } + + // Assign each Fact with a matching hash to the Contact + for _, hashFact := range c.TrigFacts { + if f, exists := hashMap[string(hashFact.Hash)]; exists { + contacts[i].Facts = append(contacts[i].Facts, f) + } + } + } + + return contacts, nil +} diff --git a/ud/search_test.go b/ud/search_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ee6b77658bd8ebe8fb176514f9698052bc0d4e32 --- /dev/null +++ b/ud/search_test.go @@ -0,0 +1,244 @@ +package ud + +import ( + "github.com/golang/protobuf/proto" + "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/storage" + "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" + "reflect" + "testing" + "time" +) + +// Happy path. +func TestManager_Search(t *testing.T) { + // 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), + } + + // 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 + 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)}, + }, + }, + } + 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) + 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: + if cb.err != nil { + 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) { + t.Errorf("Failed to get expected Contacts."+ + "\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) { + // 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), + } + + // 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) { + // 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), + } + + // 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.") + } +}