diff --git a/ud/comms.go b/ud/comms.go index 4d4ffd07af33c1d6d2c7f806426913a381fd3c5e..f8a83e74d4a9ef7e4a71c2dffb831618594bd639 100644 --- a/ud/comms.go +++ b/ud/comms.go @@ -8,43 +8,63 @@ import ( ) // Comms is a sub-interface of the client.Comms interface. This contains -// RPCs relevant to -// todo: docsting on what it is, why it's needed. This is half finished as is +// RPCs and methods relevant to sending to the UD service. type Comms interface { - // todo: docsting on what it is, why it's needed + // SendRegisterUser is the gRPC send function for the user registering + // their username with the UD service. SendRegisterUser(host *connect.Host, message *pb.UDBUserRegistration) (*messages.Ack, error) - // todo: docsting on what it is, why it's needed + // SendRegisterFact is the gRPC send function for the user registering + // a fact.Fact (email/phone number) with the UD service. SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) - // todo: docsting on what it is, why it's needed + // SendConfirmFact is the gRPC send function for the user confirming + // their fact.Fact has been registered successfully with the UD service. SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) - // todo: docsting on what it is, why it's needed + // SendRemoveFact is the gRPC send function for the user removing + // a registered fact.Fact from the UD service. This fact.Fact must be + // owned by the user. SendRemoveFact(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) - // todo: docsting on what it is, why it's needed + // SendRemoveUser is the gRPC send function for the user removing + // their username from the UD service. SendRemoveUser(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) - // todo: docsting on what it is, why it's needed + // AddHost is a function which adds a connect.Host object to the internal + // comms manager. This will be used here exclusively for adding + // the UD service if it does not currently exist within the internal + // manger. AddHost(hid *id.ID, address string, cert []byte, params connect.HostParams) (host *connect.Host, err error) - // todo: docsting on what it is, why it's needed + // GetHost retrieves a connect.Host object from the internal comms manager. + // This will be used exclusively to retrieve the UD service's connect.Host + // object. This will be used to send to the UD service on the above + // gRPC send functions. GetHost(hostId *id.ID) (*connect.Host, bool) } -// todo: docsting on what it is, why it's needed +// removeFactComms is a sub-interface of the Comms interface for the +// removeFact comm. type removeFactComms interface { SendRemoveFact(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) } +// removeUserComms is a sub-interface of the Comms interface for the +// removeUser comm. type removeUserComms interface { SendRemoveUser(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) } +// confirmFactComm is a sub-interface of the Comms interface for the +// confirmFact comm. type confirmFactComm interface { SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) } +// registerUserComms is a sub-interface of the Comms interface for the +// registerUser comm. type registerUserComms interface { SendRegisterUser(*connect.Host, *pb.UDBUserRegistration) (*messages.Ack, error) } +// addFactComms is a sub-interface of the Comms interface for the +// addFact comms type addFactComms interface { SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) } diff --git a/ud/interfaces.go b/ud/interfaces.go index 02d01f2159580699273b2e1e2d608700e2391ad5..c6507f2bb244ad003df9e47cb99c4aed285988b7 100644 --- a/ud/interfaces.go +++ b/ud/interfaces.go @@ -1,23 +1,32 @@ package ud import ( + "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" - "gitlab.com/elixxir/client/interfaces/user" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" ) -type Userinfo interface { +// UserInfo is an interface for the user.User object. +type UserInfo interface { PortableUserInfo() user.Info GetUsername() (string, error) GetReceptionRegistrationValidationSignature() []byte } +// FollowerService is an interface for the api.Client's +// NetworkFollowerStatus method. +type FollowerService interface { + NetworkFollowerStatus() api.Status +} + +// todo: this may not be needed. if so, remove. type SingleInterface interface { TransmitRequest(recipient contact.Contact, tag string, payload []byte, callback single.Response, param single.RequestParams, net cmix.Client, rng csprng.Source, diff --git a/ud/lookup.go b/ud/lookup.go index 5f64ab3de53e4469e78308f155ce208a2aa7c8d6..4ad72cc51144fe8ed47694d04f9c68315d1f4eb1 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -23,24 +23,27 @@ const LookupTag = "xxNetwork_UdLookup" // TODO: reconsider where this comes from const maxLookupMessages = 20 +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 Lookup(udContact contact.Contact, services cmix.Client, - callback single.Response, + callback lookupCallback, rng *fastRNG.StreamGenerator, uid *id.ID, grp *cyclic.Group, - timeout time.Duration) error { + timeout time.Duration) (id.Round, + receptionID.EphemeralIdentity, error) { jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout) - return lookup(services, callback, rng, uid, grp, timeout, udContact) + return lookup(services, rng, uid, grp, timeout, udContact, callback) } // BatchLookup performs a Lookup operation on a list of user IDs. // The lookup performs a callback on each lookup on the returned contact object // constructed from the response. func BatchLookup(udContact contact.Contact, - services cmix.Client, callback single.Response, + services cmix.Client, callback lookupCallback, rng *fastRNG.StreamGenerator, uids []*id.ID, grp *cyclic.Group, timeout time.Duration) { @@ -48,9 +51,11 @@ func BatchLookup(udContact contact.Contact, for _, uid := range uids { go func(localUid *id.ID) { - err := lookup(services, callback, rng, localUid, grp, timeout, udContact) + rid, ephId, err := lookup(services, rng, localUid, grp, + timeout, udContact, callback) if err != nil { - jww.WARN.Printf("Failed batch lookup on user %s: %v", localUid, err) + jww.WARN.Printf("Failed batch lookup on user %s: %v", + localUid, err) } }(uid) } @@ -61,80 +66,81 @@ func BatchLookup(udContact contact.Contact, // lookup is a helper function which sends a lookup request to the user discovery // service. It will construct a contact object off of the returned public key. // The callback will be called on that contact object. -func lookup(services cmix.Client, callback single.Response, +func lookup(net cmix.Client, rng *fastRNG.StreamGenerator, uid *id.ID, grp *cyclic.Group, - timeout time.Duration, udContact contact.Contact) error { + timeout time.Duration, udContact contact.Contact, + callback lookupCallback) (id.Round, + receptionID.EphemeralIdentity, error) { // 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 lookup request.") + return id.Round(0), + receptionID.EphemeralIdentity{}, errors.WithMessage(err, + "Failed to form outgoing lookup request.") } - // todo: figure out callback structure, maybe you do not pass - // in a single.Response but a manager callback? - f := func(payload []byte, receptionID receptionID.EphemeralIdentity, - round rounds.Round, err error) { - m.lookupResponseProcess(payload, receptionID, round, err) + response := lookupResponse{ + cb: callback, } p := single.RequestParams{ - Timeout: timeout, - MaxMessages: maxLookupMessages, - CmixParam: cmix.GetDefaultCMIXParams(), + Timeout: timeout, + MaxResponseMessages: maxLookupMessages, + CmixParam: cmix.GetDefaultCMIXParams(), } stream := rng.GetStream() defer stream.Close() - rndId, ephId, err := single.TransmitRequest(udContact, LookupTag, requestMarshaled, - callback, p, services, stream, + return single.TransmitRequest(udContact, LookupTag, requestMarshaled, + response, p, net, stream, grp) - if err != nil { - return errors.WithMessage(err, "Failed to transmit lookup request.") - } - - return nil } -// lookupResponseProcess processes the lookup response. The returned public key +// lookupResponse processes the lookup response. The returned public key // and the user ID will be constructed into a contact object. The contact object // will be passed into the callback. -func (m *Manager) lookupResponseProcess(uid *id.ID, cb single.Response, - payload []byte, err error) { - grp := m.e2e.GetGroup() +type lookupResponse struct { + cb lookupCallback + uid *id.ID + grp *cyclic.Group +} + +func (m lookupResponse) Callback(payload []byte, + receptionID receptionID.EphemeralIdentity, + round rounds.Round, err error) { if err != nil { - go cb.Callback(contact.Contact{}, errors.WithMessage(err, "Failed to lookup.")) + go m.cb(contact.Contact{}, errors.WithMessage(err, "Failed to lookup.")) return } // Unmarshal the message - lookupResponse := &LookupResponse{} - if err := proto.Unmarshal(payload, lookupResponse); err != nil { + lr := &LookupResponse{} + if err := proto.Unmarshal(payload, lr); err != nil { jww.WARN.Printf("Dropped a lookup response from user discovery due to "+ "failed unmarshal: %s", err) } - if lookupResponse.Error != "" { + if lr.Error != "" { err = errors.Errorf("User Discovery returned an error on lookup: %s", - lookupResponse.Error) - go callback(contact.Contact{}, err) + lr.Error) + go m.cb(contact.Contact{}, err) return } c := contact.Contact{ - ID: uid, - DhPubKey: grp.NewIntFromBytes(lookupResponse.PubKey), + ID: m.uid, + DhPubKey: m.grp.NewIntFromBytes(lr.PubKey), } - if lookupResponse.Username != "" { + if lr.Username != "" { c.Facts = fact.FactList{{ - Fact: lookupResponse.Username, + Fact: lr.Username, T: fact.Username, }} } - go callback(c, nil) -} + go m.cb(c, nil) +} \ No newline at end of file diff --git a/ud/manager.go b/ud/manager.go index b5d8d4d7d5babd0ed183a0a84b8585463698114d..16b9edbfd5deec067139b44b3ae342b7d8596724 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -10,12 +10,10 @@ import ( "gitlab.com/elixxir/client/event" "gitlab.com/elixxir/client/storage/versioned" store "gitlab.com/elixxir/client/ud/store/ud" - "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" "math" "time" @@ -35,11 +33,11 @@ type Manager struct { // in this object and the object itself services cmix.Client e2e e2e.Handler - events event.Manager + events *event.Manager store *store.Store // todo: find a way to remove this, maybe just pass user into object (?) - user Userinfo + user UserInfo comms Comms rng *fastRNG.StreamGenerator @@ -61,14 +59,13 @@ type alternateUd struct { // NewManager builds a new user discovery manager. It requires that an updated // NDF is available and will error if one is not. // todo: docstring, organize the order of arguments in a meaningful way -func NewManager(services cmix.Client, e2e e2e.Handler, - events event.Manager, comms Comms, userStore Userinfo, +func NewManager(services cmix.Client, e2e e2e.Handler, follower FollowerService, + events *event.Manager, comms Comms, userStore UserInfo, rng *fastRNG.StreamGenerator, username string, kv *versioned.KV) (*Manager, error) { jww.INFO.Println("ud.NewManager()") - // fixme: figuring out a way to avoid importing api would be nice - if client.NetworkFollowerStatus() != api.Running { + if follower.NetworkFollowerStatus() != api.Running { return nil, errors.New( "cannot start UD Manager when network follower is not running.") } @@ -123,9 +120,13 @@ func NewManager(services cmix.Client, e2e e2e.Handler, // NewManagerFromBackup builds a new user discover manager from a backup. // It will construct a manager that is already registered and restore // already registered facts into store. -func NewManagerFromBackup(services cmix.Client, e2e e2e.Handler, comms Comms, userStore Userinfo, rng *fastRNG.StreamGenerator, email, phone fact.Fact, kv *versioned.KV) (*Manager, error) { +func NewManagerFromBackup(services cmix.Client, + follower FollowerService, + e2e e2e.Handler, events *event.Manager, comms Comms, + userStore UserInfo, rng *fastRNG.StreamGenerator, + email, phone fact.Fact, kv *versioned.KV) (*Manager, error) { jww.INFO.Println("ud.NewManagerFromBackup()") - if client.NetworkFollowerStatus() != api.Running { + if follower.NetworkFollowerStatus() != api.Running { return nil, errors.New( "cannot start UD Manager when " + "network follower is not running.") @@ -134,6 +135,7 @@ func NewManagerFromBackup(services cmix.Client, e2e e2e.Handler, comms Comms, us m := &Manager{ services: services, e2e: e2e, + events: events, comms: comms, user: userStore, rng: rng, @@ -180,9 +182,9 @@ func NewManagerFromBackup(services cmix.Client, e2e e2e.Handler, comms Comms, us return m, nil } -func LoadManager(services cmix.Client, e2e e2e.Handler, events event.Manager, - comms Comms, userStore Userinfo, rng *fastRNG.StreamGenerator, - privKey *rsa.PrivateKey, kv *versioned.KV) (*Manager, error) { +func LoadManager(services cmix.Client, e2e e2e.Handler, + events *event.Manager, comms Comms, userStore UserInfo, + rng *fastRNG.StreamGenerator, kv *versioned.KV) (*Manager, error) { m := &Manager{ services: services, diff --git a/ud/registered.go b/ud/registered.go index ad1854498d565aab452a772231f41274025ac0e5..47104cc2f56315fcbe11dbf925f7d415f2dbf132 100644 --- a/ud/registered.go +++ b/ud/registered.go @@ -13,7 +13,7 @@ const isRegisteredVersion = 0 // isRegistered loads from storage if the client is registered with user // discovery. func (m *Manager) isRegistered() bool { - obj, err := m.kv.Get(isRegisteredKey, isRegisteredVersion) + _, err := m.kv.Get(isRegisteredKey, isRegisteredVersion) if err != nil { return false } diff --git a/ud/search.go b/ud/search.go index 836278a66f33bde3a81069716c8be721996f770b..4e48731bc51e60982ccb8c6a84205931e0a01642 100644 --- a/ud/search.go +++ b/ud/search.go @@ -6,6 +6,8 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" "gitlab.com/elixxir/client/event" "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/crypto/contact" @@ -32,10 +34,11 @@ type searchCallback func([]contact.Contact, error) // Instead, it is intended to be used to search for a user where multiple pieces // of information is known. func Search(list fact.FactList, - services cmix.Client, events event.Manager, + services cmix.Client, events *event.Manager, callback searchCallback, rng *fastRNG.StreamGenerator, udContact contact.Contact, - grp *cyclic.Group, timeout time.Duration) error { + grp *cyclic.Group, timeout time.Duration) (id.Round, + receptionID.EphemeralIdentity, error) { jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout) factHashes, factMap := hashFactList(list) @@ -44,26 +47,32 @@ func Search(list fact.FactList, request := &SearchSend{Fact: factHashes} requestMarshaled, err := proto.Marshal(request) if err != nil { - return errors.WithMessage(err, "Failed to form outgoing search request.") + return id.Round(0), receptionID.EphemeralIdentity{}, + errors.WithMessage(err, "Failed to form outgoing search request.") } - f := func(payload []byte, err error) { - m.searchResponseHandler(factMap, callback, payload, err) + response := searchResponse{ + cb: callback, + services: services, + events: events, + grp: grp, + factMap: factMap, } stream := rng.GetStream() defer stream.Close() p := single.RequestParams{ - Timeout: timeout, - MaxMessages: maxLookupMessages, - CmixParam: cmix.GetDefaultCMIXParams(), + Timeout: timeout, + MaxResponseMessages: maxSearchMessages, + CmixParam: cmix.GetDefaultCMIXParams(), } - rndId, ephId, err := single.TransmitRequest(udContact, LookupTag, requestMarshaled, - f, p, services, stream, grp) + rndId, ephId, err := single.TransmitRequest(udContact, SearchTag, requestMarshaled, + response, p, services, stream, grp) if err != nil { - return errors.WithMessage(err, "Failed to transmit search request.") + return id.Round(0), receptionID.EphemeralIdentity{}, + errors.WithMessage(err, "Failed to transmit search request.") } if events != nil { @@ -71,49 +80,58 @@ func Search(list fact.FactList, fmt.Sprintf("Sent: %+v", request)) } - return nil + return rndId, ephId, err } -func (m *Manager) searchResponseHandler(factMap map[string]fact.Fact, - callback searchCallback, payload []byte, err error) { +type searchResponse struct { + cb searchCallback + services cmix.Client + events *event.Manager + grp *cyclic.Group + factMap map[string]fact.Fact +} + +func (m searchResponse) Callback(payload []byte, + receptionID receptionID.EphemeralIdentity, + round rounds.Round, err error) { if err != nil { - go callback(nil, errors.WithMessage(err, "Failed to search.")) + go m.cb(nil, errors.WithMessage(err, "Failed to search.")) return } // Unmarshal the message - searchResponse := &SearchResponse{} - if err := proto.Unmarshal(payload, searchResponse); err != nil { + sr := &SearchResponse{} + if err := proto.Unmarshal(payload, sr); err != nil { jww.WARN.Printf("Dropped a search response from user discovery due to "+ "failed unmarshal: %s", err) } - if m.services != nil { + if m.events != nil { m.events.Report(1, "UserDiscovery", "SearchResponse", - fmt.Sprintf("Received: %+v", searchResponse)) + fmt.Sprintf("Received: %+v", sr)) } - if searchResponse.Error != "" { + if sr.Error != "" { err = errors.Errorf("User Discovery returned an error on search: %s", - searchResponse.Error) - go callback(nil, err) + sr.Error) + go m.cb(nil, err) return } // return an error if no facts are found - if len(searchResponse.Contacts) == 0 { - go callback(nil, errors.New("No contacts found in search")) + if len(sr.Contacts) == 0 { + go m.cb(nil, errors.New("No contacts found in search")) } - c, err := m.parseContacts(searchResponse.Contacts, factMap) + c, err := parseContacts(m.grp, sr.Contacts, m.factMap) if err != nil { - go callback(nil, errors.WithMessage(err, "Failed to parse contacts from "+ + go m.cb(nil, errors.WithMessage(err, "Failed to parse contacts from "+ "remote server.")) return } - go callback(c, nil) + go m.cb(c, nil) } // hashFactList hashes each fact in the FactList into a HashFact and returns a @@ -136,10 +154,9 @@ 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, +func parseContacts(grp *cyclic.Group, response []*Contact, hashMap map[string]fact.Fact) ([]contact.Contact, error) { contacts := make([]contact.Contact, len(response)) - grp := m.e2e.GetGroup() // Convert each contact message into a new contact.Contact for i, c := range response { // Unmarshal user ID bytes diff --git a/ud/store/ud/facts.go b/ud/store/ud/facts.go deleted file mode 100644 index 4346a347b47cfd03599fb8cc11728b2c6ba8ab3e..0000000000000000000000000000000000000000 --- a/ud/store/ud/facts.go +++ /dev/null @@ -1,190 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package store - -import ( - "fmt" - "github.com/pkg/errors" - "gitlab.com/elixxir/primitives/fact" -) - -const ( - factTypeExistsErr = "Fact %v cannot be added as fact type %s has already been stored. Cancelling backup operation!" - backupMissingInvalidFactTypeErr = "BackUpMissingFacts expects input in the order (email, phone). " + - "%s (%s) is non-empty but not an email. Cancelling backup operation" - backupMissingAllZeroesFactErr = "Cannot backup missing facts: Both email and phone facts are empty!" - factNotInStoreErr = "Fact %v does not exist in store" -) - -// StoreUnconfirmedFact stores a fact that has been added to UD but has not been -// confirmed by the user. It is keyed on the confirmation ID given by UD. -func (s *Store) StoreUnconfirmedFact(confirmationId string, f fact.Fact) error { - s.mux.Lock() - defer s.mux.Unlock() - - s.unconfirmedFacts[confirmationId] = f - return s.saveUnconfirmedFacts() -} - -// ConfirmFact will delete the fact from the unconfirmed store and -// add it to the confirmed fact store. The Store will then be saved -func (s *Store) ConfirmFact(confirmationId string) error { - s.mux.Lock() - defer s.mux.Unlock() - - f, exists := s.unconfirmedFacts[confirmationId] - if !exists { - return errors.New(fmt.Sprintf("No fact exists in store "+ - "with confirmation ID %q", confirmationId)) - } - - delete(s.unconfirmedFacts, confirmationId) - s.confirmedFacts[f] = struct{}{} - return s.save() -} - -// BackUpMissingFacts adds a registered fact to the Store object. It can take in both an -// email and a phone number. One or the other may be an empty string, however both is considered -// an error. It checks for each whether that fact type already exists in the structure. If a fact -// type already exists, an error is returned. -// ************************************************************************ -// NOTE: This is done since BackUpMissingFacts is exposed to the -// bindings layer. This prevents front end from using this as the method -// to store facts on their end, which is not its intended use case. It's intended use -// case is to store already registered facts, prior to the creation of this function. -// We handle storage of newly registered internally using Store.ConfirmFact. -// ************************************************************************ -// Any other fact.FactType is not accepted and returns an error and nothing is backed up. -// If you attempt to back up a fact type that has already been backed up, -// an error will be returned and nothing will be backed up. -// Otherwise, it adds the fact and returns whether the Store saved successfully. -func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error { - s.mux.Lock() - defer s.mux.Unlock() - - if isFactZero(email) && isFactZero(phone) { - return errors.New(backupMissingAllZeroesFactErr) - } - - modifiedEmail, modifiedPhone := false, false - - // Handle email if it is not zero (empty string) - if !isFactZero(email) { - // check if fact is expected type - if email.T != fact.Email { - return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Email, email.Fact)) - } - - // Check if fact type is already in map. See docstring NOTE for explanation - if isFactTypeInMap(fact.Email, s.confirmedFacts) { - // If an email exists in memory, return an error - return errors.Errorf(factTypeExistsErr, email, fact.Email) - } else { - modifiedEmail = true - } - } - - if !isFactZero(phone) { - // check if fact is expected type - if phone.T != fact.Phone { - return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Phone, phone.Fact)) - } - - // Check if fact type is already in map. See docstring NOTE for explanation - if isFactTypeInMap(fact.Phone, s.confirmedFacts) { - // If a phone exists in memory, return an error - return errors.Errorf(factTypeExistsErr, phone, fact.Phone) - } else { - modifiedPhone = true - } - } - - if modifiedPhone || modifiedEmail { - if modifiedEmail { - s.confirmedFacts[email] = struct{}{} - } - - if modifiedPhone { - s.confirmedFacts[phone] = struct{}{} - } - - return s.saveConfirmedFacts() - } - - return nil - -} - -// DeleteFact is our internal use function which will delete the registered fact -// from memory and storage. An error is returned if the fact does not exist in -// memory. -func (s *Store) DeleteFact(f fact.Fact) error { - s.mux.Lock() - defer s.mux.Unlock() - - if _, exists := s.confirmedFacts[f]; !exists { - return errors.Errorf(factNotInStoreErr, f) - } - - delete(s.confirmedFacts, f) - return s.saveConfirmedFacts() -} - -// GetStringifiedFacts returns a list of stringified facts from the Store's -// confirmedFacts map. -func (s *Store) GetStringifiedFacts() []string { - s.mux.RLock() - defer s.mux.RUnlock() - - return s.serializeConfirmedFacts() -} - -// GetFacts returns a list of fact.Fact objects that exist within the -// Store's confirmedFacts map. -func (s *Store) GetFacts() []fact.Fact { - s.mux.RLock() - defer s.mux.RUnlock() - - // Flatten the facts into a slice - facts := make([]fact.Fact, 0, len(s.confirmedFacts)) - for f := range s.confirmedFacts { - facts = append(facts, f) - } - - return facts -} - -// serializeConfirmedFacts is a helper function which serializes Store's confirmedFacts -// map into a list of strings. Each string in the list represents -// a fact.Fact that has been Stringified. -func (s *Store) serializeConfirmedFacts() []string { - fStrings := make([]string, 0, len(s.confirmedFacts)) - for f := range s.confirmedFacts { - fStrings = append(fStrings, f.Stringify()) - } - - return fStrings -} - -// fixme: consider this being a method on the fact.Fact object? -// isFactZero tests whether a fact has been uninitialized. -func isFactZero(f fact.Fact) bool { - return f.T == fact.Username && f.Fact == "" -} - -// isFactTypeInMap is a helper function which determines whether a fact type exists within -// the data structure. -func isFactTypeInMap(factType fact.FactType, facts map[fact.Fact]struct{}) bool { - for f := range facts { - if f.T == factType { - return true - } - } - - return false -} diff --git a/ud/store/ud/facts_test.go b/ud/store/ud/facts_test.go deleted file mode 100644 index 071b04b1d90a3e3702dd68d2746caa63db503209..0000000000000000000000000000000000000000 --- a/ud/store/ud/facts_test.go +++ /dev/null @@ -1,296 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 xx network SEZC // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -/////////////////////////////////////////////////////////////////////////////// - -package store - -import ( - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/ekv" - "gitlab.com/elixxir/primitives/fact" - "reflect" - "sort" - "testing" -) - -func TestNewStore(t *testing.T) { - - kv := versioned.NewKV(make(ekv.Memstore)) - - _, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - -} - -func TestStore_ConfirmFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - confirmId := "confirm" - - expected := fact.Fact{ - Fact: "josh", - T: fact.Username, - } - - err = expectedStore.StoreUnconfirmedFact(confirmId, expected) - if err != nil { - t.Fatalf("StoreUnconfirmedFact error: %v", err) - } - - err = expectedStore.ConfirmFact(confirmId) - if err != nil { - t.Fatalf("ConfirmFact() produced an error: %v", err) - } - - _, exists := expectedStore.confirmedFacts[expected] - if !exists { - t.Fatalf("Fact %s does not exist in map", expected) - } - - // Check that fact was removed from unconfirmed - _, exists = expectedStore.unconfirmedFacts[confirmId] - if exists { - t.Fatalf("Confirmed fact %v should be removed from unconfirmed"+ - " map", expected) - } -} - -func TestStore_StoreUnconfirmedFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - confirmId := "confirm" - - expected := fact.Fact{ - Fact: "josh", - T: fact.Username, - } - - err = expectedStore.StoreUnconfirmedFact(confirmId, expected) - if err != nil { - t.Fatalf("StoreUnconfirmedFact error: %v", err) - } - - // Check that fact exists in unconfirmed - _, exists := expectedStore.unconfirmedFacts[confirmId] - if !exists { - t.Fatalf("Confirmed fact %v should be removed from unconfirmed"+ - " map", expected) - } -} - -func TestStore_DeleteFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - expected := fact.Fact{ - Fact: "josh", - T: fact.Username, - } - - expectedStore.confirmedFacts[expected] = struct{}{} - - _, exists := expectedStore.confirmedFacts[expected] - if !exists { - t.Fatalf("Fact %s does not exist in map", expected) - } - - err = expectedStore.DeleteFact(expected) - if err != nil { - t.Fatalf("DeleteFact() produced an error: %v", err) - } - - err = expectedStore.DeleteFact(expected) - if err == nil { - t.Fatalf("DeleteFact should produce an error when deleting a fact not in store") - } - -} - -func TestStore_BackUpMissingFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - email := fact.Fact{ - Fact: "josh@elixxir.io", - T: fact.Email, - } - - phone := fact.Fact{ - Fact: "6175555678", - T: fact.Phone, - } - - err = expectedStore.BackUpMissingFacts(email, phone) - if err != nil { - t.Fatalf("BackUpMissingFacts() produced an error: %v", err) - } - - _, exists := expectedStore.confirmedFacts[email] - if !exists { - t.Fatalf("Fact %v not found in store.", email) - } - - _, exists = expectedStore.confirmedFacts[phone] - if !exists { - t.Fatalf("Fact %v not found in store.", phone) - } - -} - -func TestStore_BackUpMissingFacts_DuplicateFactType(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - email := fact.Fact{ - Fact: "josh@elixxir.io", - T: fact.Email, - } - - phone := fact.Fact{ - Fact: "6175555678", - T: fact.Phone, - } - - err = expectedStore.BackUpMissingFacts(email, phone) - if err != nil { - t.Fatalf("BackUpMissingFacts() produced an error: %v", err) - } - - err = expectedStore.BackUpMissingFacts(email, fact.Fact{}) - if err == nil { - t.Fatalf("BackUpMissingFacts() should not allow backing up an "+ - "email when an email has already been backed up: %v", err) - } - - err = expectedStore.BackUpMissingFacts(fact.Fact{}, phone) - if err == nil { - t.Fatalf("BackUpMissingFacts() should not allow backing up a "+ - "phone number when a phone number has already been backed up: %v", err) - } - -} - -func TestStore_GetFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - testStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - emailFact := fact.Fact{ - Fact: "josh@elixxir.io", - T: fact.Email, - } - - emptyFact := fact.Fact{} - - err = testStore.BackUpMissingFacts(emailFact, emptyFact) - if err != nil { - t.Fatalf("Faild to add fact %v: %v", emailFact, err) - } - - phoneFact := fact.Fact{ - Fact: "6175555212", - T: fact.Phone, - } - - err = testStore.BackUpMissingFacts(emptyFact, phoneFact) - if err != nil { - t.Fatalf("Faild to add fact %v: %v", phoneFact, err) - } - - expectedFacts := []fact.Fact{emailFact, phoneFact} - - receivedFacts := testStore.GetFacts() - - sort.SliceStable(receivedFacts, func(i, j int) bool { - return receivedFacts[i].Fact > receivedFacts[j].Fact - }) - - sort.SliceStable(expectedFacts, func(i, j int) bool { - return expectedFacts[i].Fact > expectedFacts[j].Fact - }) - - if !reflect.DeepEqual(expectedFacts, receivedFacts) { - t.Fatalf("GetFacts() did not return expected fact list."+ - "\nExpected: %v"+ - "\nReceived: %v", expectedFacts, receivedFacts) - } -} - -func TestStore_GetFactStrings(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - testStore, err := NewOrLoadStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - emailFact := fact.Fact{ - Fact: "josh@elixxir.io", - T: fact.Email, - } - - emptyFact := fact.Fact{} - - err = testStore.BackUpMissingFacts(emailFact, emptyFact) - if err != nil { - t.Fatalf("Faild to add fact %v: %v", emailFact, err) - } - - phoneFact := fact.Fact{ - Fact: "6175555212", - T: fact.Phone, - } - - err = testStore.BackUpMissingFacts(emptyFact, phoneFact) - if err != nil { - t.Fatalf("Faild to add fact %v: %v", phoneFact, err) - } - - expectedFacts := []string{emailFact.Stringify(), phoneFact.Stringify()} - - receivedFacts := testStore.GetStringifiedFacts() - sort.SliceStable(receivedFacts, func(i, j int) bool { - return receivedFacts[i] > receivedFacts[j] - }) - - sort.SliceStable(expectedFacts, func(i, j int) bool { - return expectedFacts[i] > expectedFacts[j] - }) - - if !reflect.DeepEqual(expectedFacts, receivedFacts) { - t.Fatalf("GetStringifiedFacts() did not return expected fact list."+ - "\nExpected: %v"+ - "\nReceived: %v", expectedFacts, receivedFacts) - } - -} diff --git a/ud/store/ud/store.go b/ud/store/ud/store.go deleted file mode 100644 index 08178e9243561fcaf067e5b77d2b970a47af1cb6..0000000000000000000000000000000000000000 --- a/ud/store/ud/store.go +++ /dev/null @@ -1,271 +0,0 @@ -package store - -// This file handles the storage operations on facts. - -import ( - "encoding/json" - "github.com/pkg/errors" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/primitives/fact" - "gitlab.com/xx_network/primitives/netTime" - "strings" - "sync" -) - -// Storage constants -const ( - version = 0 - prefix = "udStorePrefix" - unconfirmedFactKey = "unconfirmedFactKey" - confirmedFactKey = "confirmedFactKey" -) - -// todo: reorganize the contents of these files or rename these files -// also a refactor would be cool cause store.Store on the higher level -// is stuttering. - -// Error constants -const ( - malformedFactErr = "Failed to load due to " + - "malformed fact" - loadConfirmedFactErr = "Failed to load confirmed facts" - loadUnconfirmedFactErr = "Failed to load unconfirmed facts" - saveUnconfirmedFactErr = "Failed to save unconfirmed facts" - saveConfirmedFactErr = "Failed to save confirmed facts" -) - -// Store is the storage object for the higher level ud.Manager object. -// This storage implementation is written for client side. -type Store struct { - // confirmedFacts contains facts that have been confirmed - confirmedFacts map[fact.Fact]struct{} - // Stores facts that have been added by UDB but unconfirmed facts. - // Maps confirmID to fact - unconfirmedFacts map[string]fact.Fact - kv *versioned.KV - mux sync.RWMutex -} - -// NewOrLoadStore loads the Store object from the provided versioned.KV. -func NewOrLoadStore(kv *versioned.KV) (*Store, error) { - - s := &Store{ - confirmedFacts: make(map[fact.Fact]struct{}, 0), - unconfirmedFacts: make(map[string]fact.Fact, 0), - kv: kv.Prefix(prefix), - } - - if err := s.load(); err != nil { - if strings.Contains(err.Error(), "object not found") || - strings.Contains(err.Error(), "no such file or directory") { - return s, s.save() - } - } - - return s, nil - -} - -///////////////////////////////////////////////////////////////// -// SAVE FUNCTIONS -///////////////////////////////////////////////////////////////// - -// save serializes the state within Store into byte data and stores -// that data into storage via the EKV. -func (s *Store) save() error { - - err := s.saveUnconfirmedFacts() - if err != nil { - return errors.WithMessage(err, saveUnconfirmedFactErr) - } - - err = s.saveConfirmedFacts() - if err != nil { - return errors.WithMessage(err, saveConfirmedFactErr) - } - - return nil -} - -// saveConfirmedFacts saves all the data within Store.confirmedFacts into storage. -func (s *Store) saveConfirmedFacts() error { - - data, err := s.marshalConfirmedFacts() - if err != nil { - return err - } - - // Construct versioned object - now := netTime.Now() - obj := versioned.Object{ - Version: version, - Timestamp: now, - Data: data, - } - - // Save to storage - return s.kv.Set(confirmedFactKey, version, &obj) -} - -// saveUnconfirmedFacts saves all data within Store.unconfirmedFacts into storage. -func (s *Store) saveUnconfirmedFacts() error { - data, err := s.marshalUnconfirmedFacts() - if err != nil { - return err - } - - // Construct versioned object - now := netTime.Now() - obj := versioned.Object{ - Version: version, - Timestamp: now, - Data: data, - } - - // Save to storage - return s.kv.Set(unconfirmedFactKey, version, &obj) - -} - -///////////////////////////////////////////////////////////////// -// LOAD FUNCTIONS -///////////////////////////////////////////////////////////////// - -// load is a helper function which loads all data stored in storage from -// the save operation. -func (s *Store) load() error { - - err := s.loadUnconfirmedFacts() - if err != nil { - return errors.WithMessage(err, loadUnconfirmedFactErr) - } - - err = s.loadConfirmedFacts() - if err != nil { - return errors.WithMessage(err, loadConfirmedFactErr) - } - - return nil -} - -// loadConfirmedFacts loads all confirmed facts from storage. -// It is the inverse operation of saveConfirmedFacts. -func (s *Store) loadConfirmedFacts() error { - // Pull data from storage - obj, err := s.kv.Get(confirmedFactKey, version) - if err != nil { - return err - } - - // Place the map in memory - s.confirmedFacts, err = s.unmarshalConfirmedFacts(obj.Data) - if err != nil { - return err - } - - return nil -} - -// loadUnconfirmedFacts loads all unconfirmed facts from storage. -// It is the inverse operation of saveUnconfirmedFacts. -func (s *Store) loadUnconfirmedFacts() error { - // Pull data from storage - obj, err := s.kv.Get(unconfirmedFactKey, version) - if err != nil { - return err - } - - // Place the map in memory - s.unconfirmedFacts, err = s.unmarshalUnconfirmedFacts(obj.Data) - if err != nil { - return err - } - - return nil -} - -///////////////////////////////////////////////////////////////// -// MARSHAL/UNMARSHAL FUNCTIONS -///////////////////////////////////////////////////////////////// - -// unconfirmedFactDisk is an object used to store the data of an unconfirmed fact. -// It combines the key (confirmationId) and fact data (stringifiedFact) into a -// single JSON-able object. -type unconfirmedFactDisk struct { - confirmationId string - stringifiedFact string -} - -// marshalConfirmedFacts is a marshaller which serializes the data -//// in the confirmedFacts map into a JSON. -func (s *Store) marshalConfirmedFacts() ([]byte, error) { - // Flatten confirmed facts to a list - fStrings := s.serializeConfirmedFacts() - - // Marshal to JSON - return json.Marshal(&fStrings) -} - -// marshalUnconfirmedFacts is a marshaller which serializes the data -// in the unconfirmedFacts map into a JSON. -func (s *Store) marshalUnconfirmedFacts() ([]byte, error) { - // Flatten unconfirmed facts to a list - ufdList := make([]unconfirmedFactDisk, 0, len(s.unconfirmedFacts)) - for confirmationId, f := range s.unconfirmedFacts { - ufd := unconfirmedFactDisk{ - confirmationId: confirmationId, - stringifiedFact: f.Stringify(), - } - ufdList = append(ufdList, ufd) - } - - return json.Marshal(&ufdList) -} - -// unmarshalConfirmedFacts is a function which deserializes the data from storage -// into a structure matching the confirmedFacts map. -func (s *Store) unmarshalConfirmedFacts(data []byte) (map[fact.Fact]struct{}, error) { - // Unmarshal into list - var fStrings []string - err := json.Unmarshal(data, &fStrings) - if err != nil { - return nil, err - } - - // Deserialize the list into a map - confirmedFacts := make(map[fact.Fact]struct{}, 0) - for _, fStr := range fStrings { - f, err := fact.UnstringifyFact(fStr) - if err != nil { - return nil, errors.WithMessage(err, malformedFactErr) - } - - confirmedFacts[f] = struct{}{} - } - - return confirmedFacts, nil -} - -// unmarshalUnconfirmedFacts is a function which deserializes the data from storage -// into a structure matching the unconfirmedFacts map. -func (s *Store) unmarshalUnconfirmedFacts(data []byte) (map[string]fact.Fact, error) { - // Unmarshal into list - var ufdList []unconfirmedFactDisk - err := json.Unmarshal(data, &ufdList) - if err != nil { - return nil, err - } - - // Deserialize the list into a map - unconfirmedFacts := make(map[string]fact.Fact, 0) - for _, ufd := range ufdList { - f, err := fact.UnstringifyFact(ufd.stringifiedFact) - if err != nil { - return nil, errors.WithMessage(err, malformedFactErr) - } - - unconfirmedFacts[ufd.confirmationId] = f - } - - return unconfirmedFacts, nil -} diff --git a/ud/store/ud/store_test.go b/ud/store/ud/store_test.go deleted file mode 100644 index 4fcde42f064d95d0ddcaf8bd351b861777c4c05d..0000000000000000000000000000000000000000 --- a/ud/store/ud/store_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package store - -import ( - "bytes" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/ekv" - "gitlab.com/elixxir/primitives/fact" - "reflect" - "testing" -) - -// Test it loads a Store from storage if it exists. -func TestNewOrLoadStore_LoadStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - receivedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Fatalf("NewOrLoadStore() produced an error: %v", err) - } - - if !reflect.DeepEqual(expectedStore, receivedStore) { - t.Errorf("NewOrLoadStore() returned incorrect Store."+ - "\nexpected: %#v\nreceived: %#v", expectedStore, - receivedStore) - - } - -} - -// Test that it creates a new store if an old one is not in storage. -func TestNewOrLoadStore_NewStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - receivedStore, err := NewOrLoadStore(kv) - if err != nil { - t.Fatalf("NewOrLoadStore() produced an error: %v", err) - } - - expectedStore := &Store{ - confirmedFacts: make(map[fact.Fact]struct{}, 0), - unconfirmedFacts: make(map[string]fact.Fact, 0), - kv: kv.Prefix(prefix), - } - - if !reflect.DeepEqual(expectedStore, receivedStore) { - t.Errorf("NewOrLoadStore() returned incorrect Store."+ - "\nexpected: %#v\nreceived: %#v", expectedStore, - receivedStore) - - } - -} - -func TestStore_MarshalUnmarshal_ConfirmedFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - data, err := expectedStore.kv.Get(confirmedFactKey, version) - if err != nil { - t.Errorf("get() error when getting Store from KV: %v", err) - } - - expectedData, err := expectedStore.marshalConfirmedFacts() - if err != nil { - t.Fatalf("marshalConfirmedFact error: %+v", err) - } - - if !bytes.Equal(expectedData, data.Data) { - t.Errorf("NewStore() returned incorrect Store."+ - "\nexpected: %+v\nreceived: %+v", expectedData, - data.Data) - } - - recieved, err := expectedStore.unmarshalConfirmedFacts(data.Data) - if err != nil { - t.Fatalf("unmarshalUnconfirmedFacts error: %v", err) - } - - if !reflect.DeepEqual(recieved, expectedStore.confirmedFacts) { - t.Fatalf("Marshal/Unmarshal did not produce identical data"+ - "\nExpected: %v "+ - "\nReceived: %v", expectedStore.confirmedFacts, recieved) - } -} - -func TestStore_MarshalUnmarshal_UnconfirmedFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - - expectedStore, err := NewStore(kv) - if err != nil { - t.Errorf("NewStore() produced an error: %v", err) - } - - data, err := expectedStore.kv.Get(unconfirmedFactKey, version) - if err != nil { - t.Errorf("get() error when getting Store from KV: %v", err) - } - - expectedData, err := expectedStore.marshalUnconfirmedFacts() - if err != nil { - t.Fatalf("marshalConfirmedFact error: %+v", err) - } - - if !bytes.Equal(expectedData, data.Data) { - t.Errorf("NewStore() returned incorrect Store."+ - "\nexpected: %+v\nreceived: %+v", expectedData, - data.Data) - } - - recieved, err := expectedStore.unmarshalUnconfirmedFacts(data.Data) - if err != nil { - t.Fatalf("unmarshalUnconfirmedFacts error: %v", err) - } - - if !reflect.DeepEqual(recieved, expectedStore.unconfirmedFacts) { - t.Fatalf("Marshal/Unmarshal did not produce identical data"+ - "\nExpected: %v "+ - "\nReceived: %v", expectedStore.unconfirmedFacts, recieved) - } -}