diff --git a/Makefile b/Makefile index b6f9df22644b196178f6fa17222f586149198794..64cdc581e683cdbb63240888c31193b0fe10eb3d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ update_release: GOFLAGS="" go get gitlab.com/xx_network/primitives@release GOFLAGS="" go get gitlab.com/elixxir/primitives@release GOFLAGS="" go get gitlab.com/xx_network/crypto@release - GOFLAGS="" go get gitlab.com/elixxir/crypto@release + GOFLAGS="" go get gitlab.com/elixxir/crypto@singleUseMultiPartRequest GOFLAGS="" go get gitlab.com/xx_network/comms@release GOFLAGS="" go get gitlab.com/elixxir/comms@release GOFLAGS="" go get gitlab.com/elixxir/ekv@master diff --git a/api/auth.go b/api/auth.go index f7451941a7cc1f212e18f05e041b7f62c8e62f48..50ac3131b93a6da5feba080fbbdc24846fe78a72 100644 --- a/api/auth.go +++ b/api/auth.go @@ -9,6 +9,7 @@ package api import ( "encoding/binary" + "gitlab.com/elixxir/client/e2e/ratchet/partner" "math/rand" "github.com/cloudflare/circl/dh/sidh" @@ -168,14 +169,14 @@ func (c *Client) MakePrecannedContact(precannedID uint) contact.Contact { // GetRelationshipFingerprint returns a unique 15 character fingerprint for an // E2E relationship. An error is returned if no relationship with the partner // is found. -func (c *Client) GetRelationshipFingerprint(partner *id.ID) (string, error) { - m, err := c.e2e.GetPartner(partner) +func (c *Client) GetRelationshipFingerprint(p *id.ID) (string, error) { + m, err := c.e2e.GetPartner(p) if err != nil { return "", errors.Errorf("could not get partner %s: %+v", - partner, err) + partner.ConnectionFp{}, err) } else if m == nil { return "", errors.Errorf("manager for partner %s is nil.", - partner) + p) } return m.ConnectionFingerprint().String(), nil diff --git a/bindings/ud.go b/bindings/ud.go index df34f572de41569a8c2969c24c9fc24b869348a0..23195fd751822311853a869cf92f0c4bbb2b48b5 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -9,10 +9,13 @@ package bindings import ( "fmt" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/single" "time" "github.com/pkg/errors" - "gitlab.com/elixxir/client/ud" + udPackage "gitlab.com/elixxir/client/ud" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" @@ -21,38 +24,108 @@ import ( // This package wraps the user discovery system type UserDiscovery struct { - ud *ud.Manager + ud *udPackage.Manager } -// NewUserDiscovery returns a new user discovery object. Only call this once. It must be called -// after StartNetworkFollower is called and will fail if the network has never -// been contacted. +// NewUserDiscovery returns a new user discovery object. Only call this once. +// It must be called after StartNetworkFollower is called and will fail if the +// network has never been contacted. This will auto-register with the +// UD service. You should only call this on the first instantiation of the user +// discovery manager. // This function technically has a memory leak because it causes both sides of // 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. // This must be called while start network follower is running. -func NewUserDiscovery(client *Client) (*UserDiscovery, error) { - single, err := client.getSingle() +func NewUserDiscovery(client *Client, username string) (*UserDiscovery, error) { + stream := client.api.GetRng().GetStream() + defer stream.Close() + m, err := udPackage.NewManager(client.api.GetNetworkInterface(), + client.api.GetE2e(), client.api.NetworkFollowerStatus, + client.api.GetEventManager(), + client.api.GetComms(), client.api.GetStorage(), + stream, + username, client.api.GetStorage().GetKV()) + if err != nil { - return nil, errors.WithMessage(err, "Failed to create User Discovery Manager") + return nil, errors.WithMessage(err, + "Failed to create User Discovery Manager") + } else { + return &UserDiscovery{ud: m}, nil } - m, err := ud.NewManager(&client.api, single) +} + +// LoadUserDiscovery loads the state of the UserDiscovery manager +// from disk. This is meant to be called after any app restart after the first +// instantiation of the manager by NewUserDiscovery. +func LoadUserDiscovery(client *Client) (*UserDiscovery, error) { + m, err := udPackage.LoadManager(client.api.GetNetworkInterface(), + client.api.GetE2e(), client.api.GetEventManager(), + client.api.GetComms(), client.api.GetStorage(), + client.api.GetStorage().GetKV()) if err != nil { - return nil, errors.WithMessage(err, "Failed to create User Discovery Manager") + return nil, errors.WithMessage(err, + "Failed to load User Discovery Manager") } else { return &UserDiscovery{ud: m}, nil } } -// Register registers a user with user discovery. Will return an error if the -// network signatures are malformed or if the username is taken. Usernames -// 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 { - return ud.ud.Register(username) +// NewUserDiscoveryFromBackup returns a new user discovery object. It +// wil set up the manager with the backup data. Pass into it the backed up +// facts, one email and phone number each. This will add the registered facts +// to the backed Store. Any one of these fields may be empty, +// however both fields being empty will cause an error. Any other fact that is not +// an email or phone number will return an error. You may only add a fact for the +// accepted types once each. If you attempt to back up a fact type that has already +// been backed up, an error will be returned. Anytime an error is returned, it means +// the backup was not successful. +// NOTE: Do not use this as a direct store operation. This feature is intended to add facts +// to a backend store that have ALREADY BEEN REGISTERED on the account. +// THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend. +// Only call this once. It must be called after StartNetworkFollower +// is called and will fail if the network has never been contacted. +// This function technically has a memory leak because it causes both sides of +// 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. +// This must be called while start network follower is running. +func NewUserDiscoveryFromBackup(client *Client, + email, phone string) (*UserDiscovery, error) { + + var emailFact, phoneFact fact.Fact + var err error + // Parse email as a fact, if it exists + if len(email) > 2 { + emailFact, err = fact.UnstringifyFact(email) + if err != nil { + return nil, errors.WithMessagef(err, + "Failed to parse malformed email fact: %s", email) + } + } else { + jww.WARN.Printf("Loading manager without a registered email") + } + + // Parse phone number as a fact, if it exists + if len(phone) > 2 { + phoneFact, err = fact.UnstringifyFact(phone) + if err != nil { + return nil, errors.WithMessagef(err, "Failed to parse "+ + "stringified phone fact %q", phone) + } + } else { + jww.WARN.Printf("Loading manager without a " + + "registered phone number") + } + + m, err := udPackage.NewManagerFromBackup(client.api.GetNetworkInterface(), client.api.GetE2e(), client.api.NetworkFollowerStatus, client.api.GetEventManager(), client.api.GetComms(), client.api.GetStorage(), emailFact, phoneFact, client.api.GetStorage().GetKV()) + if err != nil { + return nil, errors.WithMessage(err, + "Failed to create User Discovery Manager") + } else { + return &UserDiscovery{ud: m}, nil + } } // AddFact adds a fact for the user to user discovery. Will only succeed if the @@ -73,15 +146,17 @@ func (ud *UserDiscovery) AddFact(fStr string) (string, error) { return ud.ud.SendRegisterFact(f) } -// ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from -// AddFact while the code will come over the associated communications system +// ConfirmFact 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 { - return ud.ud.SendConfirmFact(confirmationID, code) + return ud.ud.ConfirmFact(confirmationID, code) } -// RemoveFact 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. -// Users cannot remove username facts and must instead remove the user. +// RemoveFact 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. Users cannot remove username facts and must instead +// remove the user. func (ud *UserDiscovery) RemoveFact(fStr string) error { f, err := fact.UnstringifyFact(fStr) if err != nil { @@ -91,47 +166,16 @@ func (ud *UserDiscovery) RemoveFact(fStr string) error { return ud.ud.RemoveFact(f) } -// RemoveUser deletes a user. The fact sent must be the username. +// PermanentDeleteAccount deletes a user. The fact sent must be the username. // This function preserves the username forever and makes it // unusable. -func (ud *UserDiscovery) RemoveUser(fStr string) error { +func (ud *UserDiscovery) PermanentDeleteAccount(fStr string) error { f, err := fact.UnstringifyFact(fStr) if err != nil { return errors.WithMessage(err, "Failed to remove due to "+ "malformed fact") } - return ud.ud.RemoveUser(f) -} - -//BackUpMissingFacts adds a registered fact to the Store object and saves -// it to storage. It can take in both an email or a phone number, passed into -// the function in that order. Any one of these fields may be empty, -// however both fields being empty will cause an error. Any other fact that is not -// an email or phone number will return an error. You may only add a fact for the -// accepted types once each. If you attempt to back up a fact type that has already -// been backed up, an error will be returned. Anytime an error is returned, it means -// the backup was not successful. -// NOTE: Do not use this as a direct store operation. This feature is intended to add facts -// to a backend store that have ALREADY BEEN REGISTERED on the account. -// THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend. -func (ud *UserDiscovery) BackUpMissingFacts(email, phone string) error { - var emailFact, phoneFact fact.Fact - var err error - if len(email) > 2 { - emailFact, err = fact.UnstringifyFact(email) - if err != nil { - return errors.WithMessagef(err, "Failed to parse malformed email fact: %s", email) - } - } - - if len(phone) > 2 { - phoneFact, err = fact.UnstringifyFact(phone) - if err != nil { - return errors.WithMessagef(err, "Failed to parse malformed phone fact: %s", phone) - } - } - - return ud.ud.BackUpMissingFacts(emailFact, phoneFact) + return ud.ud.PermanentDeleteAccount(f) } // SearchCallback returns the result of a search @@ -139,6 +183,8 @@ type SearchCallback interface { Callback(contacts *ContactList, error string) } +const maxSearchMessages = 20 + // Search for the passed Facts. The factList is the stringification of a // fact list object, look at /bindings/list.go for more on that object. // This will reject if that object is malformed. The SearchCallback will return @@ -146,11 +192,12 @@ 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(client *Client, + fl string, callback SearchCallback, + timeoutMS int) ([]int, error) { factList, _, err := fact.UnstringifyFactList(fl) if err != nil { - return errors.WithMessage(err, "Failed to search due to "+ + return []int{}, errors.WithMessage(err, "Failed to search due to "+ "malformed fact list") } timeout := time.Duration(timeoutMS) * time.Millisecond @@ -164,7 +211,33 @@ func (ud UserDiscovery) Search(fl string, callback SearchCallback, } callback.Callback(contactList, errStr) } - return ud.ud.Search(factList, cb, timeout) + + udContact, err := ud.ud.GetContact() + if err != nil { + return []int{}, errors.WithMessage(err, "Failed to get user discovery "+ + "contact object") + } + + stream := client.api.GetRng().GetStream() + defer stream.Close() + + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: maxSearchMessages, + CmixParam: cmix.GetDefaultCMIXParams(), + } + + rids, _, err := udPackage.Search( + client.api.GetNetworkInterface(), client.api.GetEventManager(), + stream, client.api.GetE2e().GetGroup(), udContact, + cb, factList, p) + + if err != nil { + return []int{}, errors.WithMessagef(err, + "Failed to search for facts %q", factList.Stringify()) + } + + return convertRoundIdSliceToIntSlice(rids), nil } // SingleSearchCallback returns the result of a single search @@ -178,11 +251,11 @@ 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(client *Client, f string, callback SingleSearchCallback, + timeoutMS int) ([]int, error) { fObj, err := fact.UnstringifyFact(f) if err != nil { - return errors.WithMessage(err, "Failed to single search due "+ + return []int{}, errors.WithMessage(err, "Failed to single search due "+ "to malformed fact") } timeout := time.Duration(timeoutMS) * time.Millisecond @@ -196,7 +269,32 @@ func (ud UserDiscovery) SearchSingle(f string, callback SingleSearchCallback, } callback.Callback(c, errStr) } - return ud.ud.Search([]fact.Fact{fObj}, cb, timeout) + udContact, err := ud.ud.GetContact() + if err != nil { + return []int{}, errors.WithMessage(err, "Failed to get user discovery "+ + "contact object") + } + + stream := client.api.GetRng().GetStream() + defer stream.Close() + + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: maxSearchMessages, + CmixParam: cmix.GetDefaultCMIXParams(), + } + + rids, _, err := udPackage.Search(client.api.GetNetworkInterface(), + client.api.GetEventManager(), + stream, client.api.GetE2e().GetGroup(), udContact, + cb, []fact.Fact{fObj}, p) + + if err != nil { + return []int{}, errors.WithMessagef(err, + "Failed to Search (single) for fact %q", fObj.Stringify()) + } + + return convertRoundIdSliceToIntSlice(rids), nil } // LookupCallback returns the result of a single lookup @@ -204,16 +302,32 @@ type LookupCallback interface { Callback(contact *Contact, error string) } +// convertRoundIdSliceToIntSlice is a helper function which +// converts a slice of id.Round to a slice of integers. +func convertRoundIdSliceToIntSlice(rids []id.Round) []int { + ridInts := make([]int, 0) + for _, rid := range rids { + ridInts = append(ridInts, int(rid)) + } + + return ridInts +} + +// maxLookupMessages is the maximum responses that may be received for a +// single Lookup request. +const maxLookupMessages = 20 + // Lookup the contact object associated with the given userID. The // 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(client *Client, + idBytes []byte, callback LookupCallback, + timeoutMS int) ([]int, error) { uid, err := id.Unmarshal(idBytes) if err != nil { - return errors.WithMessage(err, "Failed to lookup due to "+ + return []int{}, errors.WithMessage(err, "Failed to lookup due to "+ "malformed id") } @@ -229,7 +343,34 @@ func (ud UserDiscovery) Lookup(idBytes []byte, callback LookupCallback, callback.Callback(c, errStr) } - return ud.ud.Lookup(uid, cb, timeout) + // Retrieve user discovery contact object + udContact, err := ud.ud.GetContact() + if err != nil { + return []int{}, errors.WithMessage(err, + "Failed to get user discovery "+ + "contact object") + } + + stream := client.api.GetRng().GetStream() + defer stream.Close() + + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: maxLookupMessages, + CmixParam: cmix.GetDefaultCMIXParams(), + } + + rid, _, err := udPackage.Lookup(client.api.GetNetworkInterface(), + stream, client.api.GetE2e().GetGroup(), + udContact, + cb, uid, p) + + if err != nil { + return []int{}, errors.WithMessagef(err, + "Failed to lookup ID %q", uid) + } + + return convertRoundIdSliceToIntSlice(rid), nil } @@ -250,7 +391,8 @@ type lookupResponse struct { // This will reject if that id is malformed or if the indexing on the IDList // object is wrong. The MultiLookupCallback will return with all contacts // returned within the timeout. -func (ud UserDiscovery) MultiLookup(ids *IdList, callback MultiLookupCallback, +func (ud UserDiscovery) MultiLookup(client *Client, + ids *IdList, callback MultiLookupCallback, timeoutMS int) error { idList := make([]*id.ID, 0, ids.Len()) @@ -274,6 +416,20 @@ func (ud UserDiscovery) MultiLookup(ids *IdList, callback MultiLookupCallback, timeout := time.Duration(timeoutMS) * time.Millisecond + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: maxLookupMessages, + CmixParam: cmix.GetDefaultCMIXParams(), + } + + // Retrieve user discovery contact object + udContact, err := ud.ud.GetContact() + if err != nil { + return errors.WithMessage(err, + "Failed to get user discovery "+ + "contact object") + } + //loop through the IDs and send the lookup for i := range idList { locali := i @@ -288,7 +444,11 @@ func (ud UserDiscovery) MultiLookup(ids *IdList, callback MultiLookupCallback, } go func() { - err := ud.ud.Lookup(localID, cb, timeout) + stream := client.api.GetRng().GetStream() + defer stream.Close() + _, _, err := udPackage.Lookup(client.api.GetNetworkInterface(), + stream, client.api.GetE2e().GetGroup(), + udContact, cb, localID, p) if err != nil { results <- lookupResponse{ C: contact.Contact{}, @@ -339,7 +499,3 @@ func (ud *UserDiscovery) SetAlternativeUserDiscovery(address, cert, contactFile func (ud *UserDiscovery) UnsetAlternativeUserDiscovery() error { return ud.ud.UnsetAlternativeUserDiscovery() } - -func WrapUserDiscovery(ud *ud.Manager) *UserDiscovery { - return &UserDiscovery{ud: ud} -} diff --git a/cmd/single.go b/cmd/single.go index ce698a01a32b79138ff80e1edefe9b4d15d7cb06..c2cd177f2c01987aa03d308f2fa96dce32946e06 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -62,7 +62,7 @@ var singleCmd = &cobra.Command{ recvCh: make(chan struct { request *single.Request ephID receptionID.EphemeralIdentity - round rounds.Round + round []rounds.Round }), } @@ -142,7 +142,7 @@ type Response struct { } func (r *Response) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, - round rounds.Round, err error) { + round []rounds.Round, err error) { jww.DEBUG.Printf("Payload: %v, receptionID: %v, round: %v, err: %v", payload, receptionID, round, err) r.callbackChan <- struct { @@ -243,16 +243,16 @@ type Receiver struct { recvCh chan struct { request *single.Request ephID receptionID.EphemeralIdentity - round rounds.Round + round []rounds.Round } } func (r *Receiver) Callback(req *single.Request, ephID receptionID.EphemeralIdentity, - round rounds.Round) { + round []rounds.Round) { r.recvCh <- struct { request *single.Request ephID receptionID.EphemeralIdentity - round rounds.Round + round []rounds.Round }{ request: req, ephID: ephID, diff --git a/cmix/message/fingerprints.go b/cmix/message/fingerprints.go index e396530db3abd5179502824bb39ad1f11841f99b..dcef3a77fec43a4854978c28901a98d6374aa639 100644 --- a/cmix/message/fingerprints.go +++ b/cmix/message/fingerprints.go @@ -119,11 +119,13 @@ func (f *FingerprintsManager) DeleteClientFingerprints(clientID *id.ID) { } func RandomFingerprint(rng csprng.Source) format.Fingerprint { - fp := format.Fingerprint{} - fpBuf := make([]byte, len(fp[:])) + fpBuf := make([]byte, format.KeyFPLen) if _, err := rng.Read(fpBuf); err != nil { jww.FATAL.Panicf("Failed to generate fingerprint: %+v", err) } - copy(fp[:], fpBuf) - return fp + + // The first bit must be 0. + fpBuf[0] &= 0x7F + + return format.NewFingerprint(fpBuf) } diff --git a/e2e/ratchet/partner/utils.go b/e2e/ratchet/partner/utils.go index ffd95db26e15a0bd403eec57ce7d863d3dd3406c..0bfc02fbc28338f628d967f2f092a32c7deb951b 100644 --- a/e2e/ratchet/partner/utils.go +++ b/e2e/ratchet/partner/utils.go @@ -61,7 +61,7 @@ func (p *testManager) ReceiveRelationshipFingerprint() []byte { panic("implement me") } -func (p *testManager) ConnectionFingerprintBytes() []byte { +func (p *testManager) ConnectionFingerprintBytes() ConnectionFp { panic("implement me") } diff --git a/go.mod b/go.mod index 6a99bc114b871f87af2b56acb96e2ff603f2dda9..7e99394cf5300b8d2a17c9659890f42a16b70330 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 gitlab.com/elixxir/comms v0.0.4-0.20220323190139-9ed75f3a8b2c - gitlab.com/elixxir/crypto v0.0.7-0.20220415180223-ec8d560270a1 - gitlab.com/elixxir/ekv v0.1.7 + gitlab.com/elixxir/crypto v0.0.7-0.20220420170330-979607dcc6da + gitlab.com/elixxir/ekv v0.1.7 gitlab.com/elixxir/primitives v0.0.3-0.20220330212736-cce83b5f948f gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71 diff --git a/go.sum b/go.sum index 8b274f94dffe0f1a5eb524a70567efb96e5e82cd..68096279f9c5896d461e7d7810d4a99058a52a93 100644 --- a/go.sum +++ b/go.sum @@ -283,7 +283,6 @@ gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp0 gitlab.com/elixxir/crypto v0.0.7-0.20220222221347-95c7ae58da6b/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= gitlab.com/elixxir/crypto v0.0.7-0.20220309234716-1ba339865787 h1:+qmsWov412+Yn7AKUhTbOcDgAydNXlNLPmFpO2W5LwY= gitlab.com/elixxir/crypto v0.0.7-0.20220309234716-1ba339865787/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= -gitlab.com/elixxir/crypto v0.0.7-0.20220317172048-3de167bd9406/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= gitlab.com/elixxir/crypto v0.0.7-0.20220325215559-7489d68d7714 h1:epnov8zyFWod14MUNtGHSbZCVSkZjN4NvoiBs1TgEV8= gitlab.com/elixxir/crypto v0.0.7-0.20220325215559-7489d68d7714/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= gitlab.com/elixxir/crypto v0.0.7-0.20220325224306-705ce59288bb h1:WdlmG+KPaM2Pjo1EFiFFPYEVSMV64Di1CitQnXGWBOQ= @@ -298,14 +297,12 @@ gitlab.com/elixxir/crypto v0.0.7-0.20220331001626-1829e71edf56 h1:1HJHlRwh3dDbvw gitlab.com/elixxir/crypto v0.0.7-0.20220331001626-1829e71edf56/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= gitlab.com/elixxir/crypto v0.0.7-0.20220406193349-d25222ea3c6e h1:P+E0+AdevTNWBdqf4+covcmTrRfe6rKPLtevFrjbKQA= gitlab.com/elixxir/crypto v0.0.7-0.20220406193349-d25222ea3c6e/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= -gitlab.com/elixxir/crypto v0.0.7-0.20220414175442-6d2304df43d7 h1:xEE795GeUyQaa4lRAI8IjyH31glm2OvFgzY9eiMEr1M= -gitlab.com/elixxir/crypto v0.0.7-0.20220414175442-6d2304df43d7/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= -gitlab.com/elixxir/crypto v0.0.7-0.20220414225314-6f3eb9c073a5 h1:yw3G8ZEiWu2eSZWRQmj6nBhiJIYK3Cw2MJzDPkNHYVA= -gitlab.com/elixxir/crypto v0.0.7-0.20220414225314-6f3eb9c073a5/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= -gitlab.com/elixxir/crypto v0.0.7-0.20220415172207-7de5e3cdb340 h1:f1JsT60cKFXcHPoaOD1ohIOA22FQd42vbKjF9wrKfNs= -gitlab.com/elixxir/crypto v0.0.7-0.20220415172207-7de5e3cdb340/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= gitlab.com/elixxir/crypto v0.0.7-0.20220415180223-ec8d560270a1 h1:stzHgpYxHQu3JgvQu5Vr0hr4nzJUk5CxspZEoNx26eQ= gitlab.com/elixxir/crypto v0.0.7-0.20220415180223-ec8d560270a1/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= +gitlab.com/elixxir/crypto v0.0.7-0.20220418163058-a76028e93dd3 h1:tYr7CjBj3p4tmUmvEmsX5n0m0GsfG8eJOu1YLoBHE2g= +gitlab.com/elixxir/crypto v0.0.7-0.20220418163058-a76028e93dd3/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= +gitlab.com/elixxir/crypto v0.0.7-0.20220420170330-979607dcc6da h1:SZQTa7Gp2vPAwu3yQDhkiKPhFqQXCsiRi0s40e/em/Y= +gitlab.com/elixxir/crypto v0.0.7-0.20220420170330-979607dcc6da/go.mod h1:JkByWX/TXCjdu6pRJsx+jwttbBGvlAljYSJMImDmt+4= gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A= gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/ekv v0.1.7 h1:OW2z+N4QCqqMFzouAwFTWWMKz0Y/PDhyYReN7gQ5NiQ= diff --git a/single/cypher.go b/single/cypher.go index 8a05758319fb01afffec8de48a4f333711d8ea46..065cfe2d6525c60a8ae59b770b60b192287c4b16 100644 --- a/single/cypher.go +++ b/single/cypher.go @@ -16,15 +16,21 @@ import ( "gitlab.com/elixxir/primitives/format" ) +type newKeyFn func(dhKey *cyclic.Int, keyNum uint64) []byte +type newFpFn func(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint + // makeCyphers generates all fingerprints for a given number of messages. -func makeCyphers(dhKey *cyclic.Int, messageCount uint8) []cypher { +func makeCyphers(dhKey *cyclic.Int, messageCount uint8, newKey newKeyFn, + newFp newFpFn) []cypher { cypherList := make([]cypher, messageCount) for i := uint8(0); i < messageCount; i++ { cypherList[i] = cypher{ - dhKey: dhKey, - num: i, + dhKey: dhKey, + num: i, + newKey: newKey, + newFp: newFp, } } @@ -32,22 +38,25 @@ func makeCyphers(dhKey *cyclic.Int, messageCount uint8) []cypher { } type cypher struct { - dhKey *cyclic.Int - num uint8 + dhKey *cyclic.Int + num uint8 + newKey newKeyFn // Function used to create new key + newFp newFpFn // Function used to create new fingerprint } func (rk *cypher) getKey() []byte { - return singleUse.NewResponseKey(rk.dhKey, uint64(rk.num)) + return rk.newKey(rk.dhKey, uint64(rk.num)) } func (rk *cypher) GetFingerprint() format.Fingerprint { - return singleUse.NewResponseFingerprint(rk.dhKey, uint64(rk.num)) + return rk.newFp(rk.dhKey, uint64(rk.num)) } func (rk *cypher) Encrypt(rp message.ResponsePart) ( fp format.Fingerprint, encryptedPayload, mac []byte) { fp = rk.GetFingerprint() key := rk.getKey() + // FIXME: Encryption is identical to what is used by e2e.Crypt, lets make // them the same code path. encryptedPayload = cAuth.Crypt(key, fp[:24], rp.Marshal()) @@ -59,7 +68,7 @@ func (rk *cypher) Decrypt(contents, mac []byte) ([]byte, error) { fp := rk.GetFingerprint() key := rk.getKey() - // Verify the cMix message MAC + // Verify the CMix message MAC if !singleUse.VerifyMAC(key, contents, mac) { return nil, errors.New("failed to verify the single-use MAC") } diff --git a/single/interfaces.go b/single/interfaces.go new file mode 100644 index 0000000000000000000000000000000000000000..8ee9bf00212ff187cc5059798d7f44a2fb2a9cda --- /dev/null +++ b/single/interfaces.go @@ -0,0 +1,32 @@ +package single + +import ( + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "time" +) + +// CMix is a sub-interface of the cmix.Client. It contains the methods +// relevant to what is used in this package. +type CMix interface { + IsHealthy() bool + GetAddressSpace() uint8 + GetMaxMessageLength() int + DeleteClientFingerprints(identity *id.ID) + AddFingerprint(identity *id.ID, fingerprint format.Fingerprint, + mp message.Processor) error + AddIdentity(id *id.ID, validUntil time.Time, persistent bool) + Send(recipient *id.ID, fingerprint format.Fingerprint, + service message.Service, payload, mac []byte, cmixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) + AddService(clientID *id.ID, newService message.Service, + response message.Processor) + DeleteService(clientID *id.ID, toDelete message.Service, + processor message.Processor) + GetInstance() *network.Instance + CheckInProgressMessages() +} diff --git a/single/listener.go b/single/listener.go index bd5f7dab613f06eba040fae03d819d7779e66200..1416e335dcdb22406225bcccbe7c6ff55b7ac9ce 100644 --- a/single/listener.go +++ b/single/listener.go @@ -4,7 +4,6 @@ import ( "fmt" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/identity/receptionID" cmixMsg "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" @@ -16,8 +15,10 @@ import ( "gitlab.com/xx_network/primitives/id" ) +const listenerProcessorName = "listenerProcessorName" + type Receiver interface { - Callback(*Request, receptionID.EphemeralIdentity, rounds.Round) + Callback(*Request, receptionID.EphemeralIdentity, []rounds.Round) } type Listener interface { @@ -31,7 +32,7 @@ type listener struct { tag string grp *cyclic.Group cb Receiver - net cmix.Client + net CMix } // Listen allows a server to listen for single use requests. It will register a @@ -39,7 +40,7 @@ type listener struct { // listener can be active for a tag-myID pair, and an error will be returned if // that is violated. When requests are received, they will be called on the // Receiver interface. -func Listen(tag string, myId *id.ID, privKey *cyclic.Int, net cmix.Client, +func Listen(tag string, myId *id.ID, privKey *cyclic.Int, net CMix, e2eGrp *cyclic.Group, cb Receiver) Listener { l := &listener{ @@ -64,7 +65,6 @@ func Listen(tag string, myId *id.ID, privKey *cyclic.Int, net cmix.Client, func (l *listener) Process(ecrMsg format.Message, receptionID receptionID.EphemeralIdentity, round rounds.Round) { - // Unmarshal the cMix message contents to a request message requestMsg, err := message.UnmarshalRequest(ecrMsg.GetContents(), l.grp.GetP().ByteLen()) @@ -77,7 +77,7 @@ func (l *listener) Process(ecrMsg format.Message, // Generate DH key and symmetric key senderPubkey := requestMsg.GetPubKey(l.grp) dhKey := l.grp.Exp(senderPubkey, l.myPrivKey, l.grp.NewInt(1)) - key := singleUse.NewTransmitKey(dhKey) + key := singleUse.NewRequestKey(dhKey) // Verify the MAC if !singleUse.VerifyMAC(key, requestMsg.GetPayload(), ecrMsg.GetMac()) { @@ -89,7 +89,6 @@ func (l *listener) Process(ecrMsg format.Message, // Decrypt the request message payload fp := ecrMsg.GetKeyFP() decryptedPayload := cAuth.Crypt(key, fp[:24], requestMsg.GetPayload()) - // Unmarshal payload payload, err := message.UnmarshalRequestPayload(decryptedPayload) if err != nil { @@ -98,20 +97,61 @@ func (l *listener) Process(ecrMsg format.Message, return } - used := uint32(0) - - r := Request{ - sender: payload.GetRecipientID(requestMsg.GetPubKey(l.grp)), - senderPubKey: senderPubkey, - dhKey: dhKey, - tag: l.tag, - maxParts: 0, - used: &used, - requestPayload: payload.GetContents(), - net: l.net, + cbFunc := func(payloadContents []byte, rounds []rounds.Round) { + used := uint32(0) + + r := Request{ + sender: payload.GetRecipientID(requestMsg.GetPubKey(l.grp)), + senderPubKey: senderPubkey, + dhKey: dhKey, + tag: l.tag, + maxParts: payload.GetMaxResponseParts(), + used: &used, + requestPayload: payloadContents, + net: l.net, + } + + go l.cb.Callback(&r, receptionID, rounds) + } + + if numParts := payload.GetNumParts(); numParts > 1 { + c := message.NewCollator(numParts) + _, _, err = c.Collate(payload) + if err != nil { + + return + } + cyphers := makeCyphers(dhKey, numParts, + singleUse.NewRequestPartKey, singleUse.NewRequestPartFingerprint) + ridCollector := newRoundIdCollector(int(numParts)) + for i, cy := range cyphers { + key = singleUse.NewRequestPartKey(dhKey, uint64(i+1)) + fp = singleUse.NewRequestPartFingerprint(dhKey, uint64(i+1)) + p := &requestPartProcessor{ + myId: l.myId, + tag: l.tag, + cb: cbFunc, + c: c, + cy: cy, + roundIDs: ridCollector, + } + err = l.net.AddFingerprint(l.myId, fp, p) + if err != nil { + jww.ERROR.Printf("Failed to add fingerprint for request part "+ + "%d of %d (%s): %+v", i, numParts, l.tag, err) + return + } + } + + l.net.CheckInProgressMessages() + } else { + cbFunc(payload.GetContents(), []rounds.Round{round}) } +} + +func (l *listener) String() string { + return listenerProcessorName - go l.cb.Callback(&r, receptionID, round) } func (l *listener) Stop() { diff --git a/single/message/collator.go b/single/message/collator.go index d2eff3a366f657e1d21f3c031cabd74a3b6f0850..9e59eb9e95f00b5b4ec8b0550bf403523e130cec 100644 --- a/single/message/collator.go +++ b/single/message/collator.go @@ -9,10 +9,9 @@ import ( // Error messages. const ( // Collate - errUnmarshalResponsePart = "failed to unmarshal response payload: %+v" - errMaxParts = "max number of parts reported by payload %d is larger than collator expected (%d)" - errPartOutOfRange = "payload part number %d greater than max number of expected parts (%d)" - errPartExists = "a payload for the part number %d already exists in the list" + errMaxParts = "max number of parts reported by payload %d is larger than collator expected (%d)" + errPartOutOfRange = "payload part number %d greater than max number of expected parts (%d)" + errPartExists = "a payload for the part number %d already exists in the list" ) // Initial value of the Collator maxNum that indicates it has yet to be set. @@ -26,6 +25,17 @@ type Collator struct { sync.Mutex } +type Part interface { + // GetNumParts returns the total number of parts in the message. + GetNumParts() uint8 + + // GetPartNum returns the index of this part in the message. + GetPartNum() uint8 + + // GetContents returns the contents of the message part. + GetContents() []byte +} + // NewCollator generates an empty list of payloads to fit the max number of // possible messages. maxNum is set to indicate that it is not yet set. func NewCollator(messageCount uint8) *Collator { @@ -38,38 +48,33 @@ func NewCollator(messageCount uint8) *Collator { // Collate collects message payload parts. Once all parts are received, the full // collated payload is returned along with true. Otherwise, returns false. -func (c *Collator) Collate(payloadBytes []byte) ([]byte, bool, error) { - payload, err := UnmarshalResponsePart(payloadBytes) - if err != nil { - return nil, false, errors.Errorf(errUnmarshalResponsePart, err) - } - +func (c *Collator) Collate(part Part) ([]byte, bool, error) { c.Lock() defer c.Unlock() // If this is the first message received, then set the max number of // messages expected to be received off its max number of parts if c.maxNum == unsetCollatorMax { - if int(payload.GetNumParts()) > len(c.payloads) { + if int(part.GetNumParts()) > len(c.payloads) { return nil, false, errors.Errorf( - errMaxParts, payload.GetNumParts(), len(c.payloads)) + errMaxParts, part.GetNumParts(), len(c.payloads)) } - c.maxNum = int(payload.GetNumParts()) + c.maxNum = int(part.GetNumParts()) } // Make sure that the part number is within the expected number of parts - if int(payload.GetPartNum()) >= c.maxNum { + if int(part.GetPartNum()) >= c.maxNum { return nil, false, - errors.Errorf(errPartOutOfRange, payload.GetPartNum(), c.maxNum) + errors.Errorf(errPartOutOfRange, part.GetPartNum(), c.maxNum) } // Make sure no payload with the same part number exists - if c.payloads[payload.GetPartNum()] != nil { - return nil, false, errors.Errorf(errPartExists, payload.GetPartNum()) + if c.payloads[part.GetPartNum()] != nil { + return nil, false, errors.Errorf(errPartExists, part.GetPartNum()) } // Add the payload to the list - c.payloads[payload.GetPartNum()] = payload.GetContents() + c.payloads[part.GetPartNum()] = part.GetContents() c.count++ // Return false if not all messages have been received diff --git a/single/message/collator_test.go b/single/message/collator_test.go index 9ad8312525a246fd14e9bd21f67701cd0125af91..617697db480b6dd6f6375757b326952db17021f9 100644 --- a/single/message/collator_test.go +++ b/single/message/collator_test.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "reflect" - "strings" "testing" ) @@ -29,16 +28,17 @@ func TestNewCollator(t *testing.T) { func TestCollator_Collate(t *testing.T) { messageCount := 16 msgPayloadSize := 2 - msgParts := map[int]ResponsePart{} + msgParts := map[int]mockPart{} expectedData := make([]byte, messageCount*msgPayloadSize) copy(expectedData, "This is the expected final data.") buff := bytes.NewBuffer(expectedData) for i := 0; i < messageCount; i++ { - msgParts[i] = NewResponsePart(msgPayloadSize + 5) - msgParts[i].SetNumParts(uint8(messageCount)) - msgParts[i].SetPartNum(uint8(i)) - msgParts[i].SetContents(buff.Next(msgPayloadSize)) + msgParts[i] = mockPart{ + numParts: uint8(messageCount), + partNum: uint8(i), + contents: buff.Next(msgPayloadSize), + } } c := NewCollator(uint8(messageCount)) @@ -51,7 +51,7 @@ func TestCollator_Collate(t *testing.T) { var err error var collated bool - fullPayload, collated, err = c.Collate(part.Marshal()) + fullPayload, collated, err = c.Collate(part) if err != nil { t.Errorf("Collate returned an error for part #%d: %+v", j, err) } @@ -71,31 +71,12 @@ func TestCollator_Collate(t *testing.T) { } } -// Error path: the byte slice cannot be unmarshaled. -func TestCollator_collate_UnmarshalError(t *testing.T) { - payloadBytes := []byte{1} - c := NewCollator(1) - payload, collated, err := c.Collate(payloadBytes) - expectedErr := strings.Split(errUnmarshalResponsePart, "%")[0] - - if err == nil || !strings.Contains(err.Error(), expectedErr) { - t.Errorf("Collate failed to return an error for failing to "+ - "unmarshal the payload.\nexpected: %s\nreceived: %+v", - expectedErr, err) - } - - if payload != nil || collated { - t.Errorf("Collate signaled the payload was collated on error."+ - "\npayload: %+v\ncollated: %+v", payload, collated) - } -} - // Error path: max reported parts by payload larger than set in Collator. func TestCollator_Collate_MaxPartsError(t *testing.T) { - payloadBytes := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + p := mockPart{0xFF, 0xFF, []byte{0xFF, 0xFF, 0xFF}} messageCount := uint8(1) c := NewCollator(messageCount) - _, _, err := c.Collate(payloadBytes) + _, _, err := c.Collate(p) expectedErr := fmt.Sprintf(errMaxParts, 0xFF, messageCount) if err == nil || err.Error() != expectedErr { @@ -107,11 +88,11 @@ func TestCollator_Collate_MaxPartsError(t *testing.T) { // Error path: the message part number is greater than the max number of parts. func TestCollator_Collate_PartNumTooLargeError(t *testing.T) { - payloadBytes := []byte{25, 5, 5, 5, 5} - partNum := uint8(5) - c := NewCollator(partNum) - _, _, err := c.Collate(payloadBytes) - expectedErr := fmt.Sprintf(errPartOutOfRange, partNum, c.maxNum) + p := mockPart{35, 5, []byte{5, 5, 5}} + messageCount := uint8(1) + c := NewCollator(messageCount) + _, _, err := c.Collate(p) + expectedErr := fmt.Sprintf(errMaxParts, p.numParts, messageCount) if err == nil || err.Error() != expectedErr { t.Errorf("Collate failed to return the expected error when the part "+ @@ -122,17 +103,28 @@ func TestCollator_Collate_PartNumTooLargeError(t *testing.T) { // Error path: a message with the part number already exists. func TestCollator_Collate_PartExistsError(t *testing.T) { - payloadBytes := []byte{0, 1, 5, 0, 1, 20} + p := mockPart{5, 1, []byte{5, 0, 1, 20}} c := NewCollator(5) - _, _, err := c.Collate(payloadBytes) + _, _, err := c.Collate(p) if err != nil { t.Fatalf("Collate returned an error: %+v", err) } - expectedErr := fmt.Sprintf(errPartExists, payloadBytes[1]) + expectedErr := fmt.Sprintf(errPartExists, p.partNum) - _, _, err = c.Collate(payloadBytes) + _, _, err = c.Collate(p) if err == nil || err.Error() != expectedErr { t.Errorf("Collate failed to return an error when the part number "+ "already exists.\nexpected: %s\nreceived: %+v", expectedErr, err) } } + +type mockPart struct { + numParts uint8 + partNum uint8 + contents []byte +} + +func (m mockPart) GetNumParts() uint8 { return m.numParts } +func (m mockPart) GetPartNum() uint8 { return m.partNum } +func (m mockPart) GetContents() []byte { return m.contents } +func (m mockPart) Marshal() []byte { return append([]byte{m.numParts, m.partNum}, m.contents...) } diff --git a/single/message/request.go b/single/message/request.go index 75e83510926c7b1d559be6068b0bde1702cae34c..3519902e75b6804df0e4ca0108b396e5af79863b 100644 --- a/single/message/request.go +++ b/single/message/request.go @@ -277,11 +277,23 @@ func (mp RequestPayload) GetNumRequestParts() uint8 { return mp.numRequestParts[0] } +// GetNumParts returns the number of messages in the request. This function +// wraps GetMaxRequestParts so that RequestPayload adheres to the Part +// interface. +func (mp RequestPayload) GetNumParts() uint8 { + return mp.GetNumRequestParts() +} + // SetNumRequestParts sets the number of messages in the request. func (mp RequestPayload) SetNumRequestParts(num uint8) { copy(mp.numRequestParts, []byte{num}) } +// GetPartNum always returns 0 since it is the first message. +func (mp RequestPayload) GetPartNum() uint8 { + return 0 +} + // GetContents returns the payload's contents. func (mp RequestPayload) GetContents() []byte { return mp.contents[:binary.BigEndian.Uint16(mp.size)] diff --git a/single/message/requestPart.go b/single/message/requestPart.go new file mode 100644 index 0000000000000000000000000000000000000000..11d9f27b663cd15d500d7bf3321427c08f70c5d9 --- /dev/null +++ b/single/message/requestPart.go @@ -0,0 +1,138 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package message + +import ( + "encoding/binary" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" +) + +// Error messages. +const ( + // NewRequestPart + errReqPartPayloadSize = "[SU] Failed to create new single-use request " + + "message part: external payload size (%d) is smaller than the " + + "minimum message size for a request part (%d)." + + // UnmarshalRequestPart + errReqPartDataSize = "size of data (%d) must be at least %d" + + // RequestPart.SetContents + errReqPartContentsSize = "[SU] Failed to set contents of single-use " + + "request message part: size of the supplied contents (%d) is larger " + + "than the max message size (%d)." +) + +// Sizes of fields. +const ( + reqPartPartNumLen = 1 + reqPartSizeLen = 2 + reqPartMinSize = reqPartPartNumLen + reqPartSizeLen +) + +/* ++------------------------------+ +| cMix Message Contents | ++---------+---------+----------+ +| partNum | size | contents | +| 1 byte | 2 bytes | variable | ++---------+---------+----------+ +*/ + +type RequestPart struct { + data []byte // Serial of all contents + partNum []byte // Index of message in a series of messages + size []byte // Size of the contents + contents []byte // The encrypted contents +} + +// NewRequestPart generates a new request message part of the specified size. +func NewRequestPart(externalPayloadSize int) RequestPart { + if externalPayloadSize < reqPartMinSize { + jww.FATAL.Panicf( + errReqPartPayloadSize, externalPayloadSize, reqPartMinSize) + } + + rmp := mapRequestPart(make([]byte, externalPayloadSize)) + return rmp +} + +// GetRequestPartContentsSize returns the size of the contents for the given +// external payload size. +func GetRequestPartContentsSize(externalPayloadSize int) int { + return externalPayloadSize - reqPartMinSize +} + +// mapRequestPart builds a message part mapped to the passed in data. +// It is mapped by reference; a copy is not made. +func mapRequestPart(data []byte) RequestPart { + return RequestPart{ + data: data, + partNum: data[:reqPartPartNumLen], + size: data[reqPartPartNumLen:reqPartMinSize], + contents: data[reqPartMinSize:], + } +} + +// UnmarshalRequestPart converts a byte buffer into a request message part. +func UnmarshalRequestPart(b []byte) (RequestPart, error) { + if len(b) < reqPartMinSize { + return RequestPart{}, errors.Errorf( + errReqPartDataSize, len(b), reqPartMinSize) + } + return mapRequestPart(b), nil +} + +// Marshal returns the bytes of the message part. +func (m RequestPart) Marshal() []byte { + return m.data +} + +// GetPartNum returns the index of this part in the message. +func (m RequestPart) GetPartNum() uint8 { + return m.partNum[0] +} + +// SetPartNum sets the part number of the message. +func (m RequestPart) SetPartNum(num uint8) { + copy(m.partNum, []byte{num}) +} + +// GetNumParts always returns 0. It is here so that RequestPart adheres to th +// Part interface. +func (m RequestPart) GetNumParts() uint8 { + return 0 +} + +// GetContents returns the contents of the message part. +func (m RequestPart) GetContents() []byte { + return m.contents[:binary.BigEndian.Uint16(m.size)] +} + +// GetContentsSize returns the length of the contents. +func (m RequestPart) GetContentsSize() int { + return int(binary.BigEndian.Uint16(m.size)) +} + +// GetMaxContentsSize returns the max capacity of the contents. +func (m RequestPart) GetMaxContentsSize() int { + return len(m.contents) +} + +// SetContents sets the contents of the message part. Does not zero out previous +// contents. +func (m RequestPart) SetContents(contents []byte) { + if len(contents) > len(m.contents) { + jww.FATAL.Panicf(errReqPartContentsSize, len(contents), len(m.contents)) + } + + binary.BigEndian.PutUint16(m.size, uint16(len(contents))) + + copy(m.contents, contents) +} diff --git a/single/message/requestPart_test.go b/single/message/requestPart_test.go new file mode 100644 index 0000000000000000000000000000000000000000..75090ffd6823c5c6844fbaa60bea64057f380fca --- /dev/null +++ b/single/message/requestPart_test.go @@ -0,0 +1,183 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package message + +import ( + "bytes" + "fmt" + "math/rand" + "reflect" + "testing" +) + +// Happy path. +func Test_NewRequestPart(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + payloadSize := prng.Intn(2000) + expected := RequestPart{ + data: make([]byte, payloadSize), + partNum: make([]byte, reqPartPartNumLen), + size: make([]byte, reqPartSizeLen), + contents: make([]byte, payloadSize-reqPartMinSize), + } + + rmp := NewRequestPart(payloadSize) + + if !reflect.DeepEqual(expected, rmp) { + t.Errorf("NewRequestPart did not return the expected "+ + "RequestPart.\nexpected: %+v\nreceived: %+v", expected, rmp) + } +} + +// Error path: provided contents size is not large enough. +func Test_NewRequestPart_PayloadSizeError(t *testing.T) { + externalPayloadSize := 1 + expectedErr := fmt.Sprintf( + errReqPartPayloadSize, externalPayloadSize, reqPartMinSize) + defer func() { + if r := recover(); r == nil || r != expectedErr { + t.Errorf("NewRequestPart did not panic with the expected error "+ + "when the size of the payload is smaller than the required "+ + "size.\nexpected: %s\nreceived: %+v", expectedErr, r) + } + }() + + _ = NewRequestPart(externalPayloadSize) +} + +// Happy path. +func Test_mapRequestPart(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + expectedPartNum := uint8(prng.Uint32()) + size := []byte{uint8(prng.Uint64()), uint8(prng.Uint64())} + expectedContents := make([]byte, prng.Intn(2000)) + prng.Read(expectedContents) + var data []byte + data = append(data, expectedPartNum) + data = append(data, size...) + data = append(data, expectedContents...) + + rmp := mapRequestPart(data) + + if expectedPartNum != rmp.partNum[0] { + t.Errorf("mapRequestPart did not correctly map partNum."+ + "\nexpected: %d\nreceived: %d", expectedPartNum, rmp.partNum[0]) + } + + if !bytes.Equal(expectedContents, rmp.contents) { + t.Errorf("mapRequestPart did not correctly map contents."+ + "\nexpected: %+v\nreceived: %+v", expectedContents, rmp.contents) + } + + if !bytes.Equal(data, rmp.data) { + t.Errorf("mapRequestPart did not save the data correctly."+ + "\nexpected: %+v\nreceived: %+v", data, rmp.data) + } +} + +// Happy path. +func TestRequestPart_Marshal_UnmarshalRequestPart(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + payload := make([]byte, prng.Intn(2000)) + prng.Read(payload) + rmp := NewRequestPart(prng.Intn(2000)) + + data := rmp.Marshal() + + newRmp, err := UnmarshalRequestPart(data) + if err != nil { + t.Errorf("UnmarshalRequestPart produced an error: %+v", err) + } + + if !reflect.DeepEqual(rmp, newRmp) { + t.Errorf("Failed to Marshal and unmarshal the RequestPart."+ + "\nexpected: %+v\nrecieved: %+v", rmp, newRmp) + } +} + +// Error path: provided bytes are too small. +func Test_UnmarshalRequestPart_Error(t *testing.T) { + data := []byte{1} + expectedErr := fmt.Sprintf(errReqPartDataSize, len(data), reqPartMinSize) + _, err := UnmarshalRequestPart([]byte{1}) + if err == nil || err.Error() != expectedErr { + t.Errorf("UnmarshalRequestPart did not produce the expected error "+ + "when the byte slice is smaller required."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Happy path. +func TestRequestPart_SetPartNum_GetPartNum(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + expectedPartNum := uint8(prng.Uint32()) + rmp := NewRequestPart(prng.Intn(2000)) + + rmp.SetPartNum(expectedPartNum) + + if expectedPartNum != rmp.GetPartNum() { + t.Errorf("GetPartNum failed to return the expected part number."+ + "\nexpected: %d\nrecieved: %d", expectedPartNum, rmp.GetPartNum()) + } +} + +// Happy path. +func TestRequestPart_GetMaxParts(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + expectedMaxParts := uint8(0) + rmp := NewRequestPart(prng.Intn(2000)) + + if expectedMaxParts != rmp.GetNumParts() { + t.Errorf("GetNumParts failed to return the expected max parts."+ + "\nexpected: %d\nrecieved: %d", expectedMaxParts, rmp.GetNumParts()) + } +} + +// Happy path. +func TestRequestPart_SetContents_GetContents_GetContentsSize_GetMaxContentsSize(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + externalPayloadSize := prng.Intn(2000) + contentSize := externalPayloadSize - reqPartMinSize - 10 + expectedContents := make([]byte, contentSize) + prng.Read(expectedContents) + rmp := NewRequestPart(externalPayloadSize) + rmp.SetContents(expectedContents) + + if !bytes.Equal(expectedContents, rmp.GetContents()) { + t.Errorf("GetContents failed to return the expected contents."+ + "\nexpected: %+v\nrecieved: %+v", expectedContents, rmp.GetContents()) + } + + if contentSize != rmp.GetContentsSize() { + t.Errorf("GetContentsSize failed to return the expected contents size."+ + "\nexpected: %d\nrecieved: %d", contentSize, rmp.GetContentsSize()) + } + + if externalPayloadSize-reqPartMinSize != rmp.GetMaxContentsSize() { + t.Errorf("GetMaxContentsSize failed to return the expected max "+ + "contents size.\nexpected: %d\nrecieved: %d", + externalPayloadSize-reqPartMinSize, rmp.GetMaxContentsSize()) + } +} + +// Error path: size of supplied contents does not match message contents size. +func TestRequestPart_SetContents_ContentsSizeError(t *testing.T) { + payloadSize, contentsLen := 255, 500 + expectedErr := fmt.Sprintf( + errReqPartContentsSize, contentsLen, payloadSize-reqPartMinSize) + defer func() { + if r := recover(); r == nil || r != expectedErr { + t.Errorf("SetContents did not panic with the expected error when "+ + "the size of the supplied bytes is larger than the content "+ + "size.\nexpected: %s\nreceived: %+v", expectedErr, r) + } + }() + + rmp := NewRequestPart(payloadSize) + rmp.SetContents(make([]byte, contentsLen)) +} diff --git a/single/receivedRequest.go b/single/receivedRequest.go index 7de42ddcea880386e36d2590688c95bb1f58d6b6..c0fdb2956055958f392db7c3e80dc010d937adb5 100644 --- a/single/receivedRequest.go +++ b/single/receivedRequest.go @@ -10,6 +10,7 @@ import ( "gitlab.com/elixxir/client/single/message" ds "gitlab.com/elixxir/comms/network/dataStructures" "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/e2e/singleUse" "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" "sync" @@ -26,7 +27,7 @@ type Request struct { maxParts uint8 // Max number of messages allowed in reply used *uint32 // Atomic variable requestPayload []byte - net cmix.Client + net CMix } // GetMaxParts returns the maximum number of message parts that can be sent in a @@ -91,7 +92,8 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, parts := partitionResponse(payload, r.net.GetMaxMessageLength(), r.maxParts) // Encrypt and send the partitions - cyphers := makeCyphers(r.dhKey, uint8(len(parts))) + cyphers := makeCyphers(r.dhKey, uint8(len(parts)), + singleUse.NewResponseKey, singleUse.NewResponseFingerprint) rounds := make([]id.Round, len(parts)) sendResults := make(chan ds.EventReturn, len(parts)) @@ -102,6 +104,7 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, cmixParams.DebugTag = "single.Response" } + // fixme: should the above debug tag and the below service tag be flipped?? svc := cmixMsg.Service{ Identifier: r.dhKey.Bytes(), Tag: "single.response-dummyService", @@ -115,7 +118,8 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams, defer wg.Done() partFP, ecrPart, mac := cyphers[j].Encrypt(parts[j]) // Send Message - round, ephID, err := r.net.Send(r.sender, partFP, svc, ecrPart, mac, + round, ephID, err := r.net.Send(r.sender, partFP, svc, + ecrPart, mac, cmixParams) if err != nil { atomic.AddUint32(&failed, 1) diff --git a/single/request.go b/single/request.go index 2fa534197e5735ce631cc43fb9a77c173f6b66ae..2b8a11bb2d5fa29ce6dfea434bdee910fa5be3f9 100644 --- a/single/request.go +++ b/single/request.go @@ -1,6 +1,7 @@ package single import ( + "bytes" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/cmix" @@ -24,7 +25,7 @@ import ( // Response interface allows for callbacks to type Response interface { Callback(payload []byte, receptionID receptionID.EphemeralIdentity, - round rounds.Round, err error) + rounds []rounds.Round, err error) } type RequestParams struct { @@ -44,11 +45,13 @@ func GetDefaultRequestParams() RequestParams { // Error messages. const ( // TransmitRequest - errNetworkHealth = "cannot send singe-use request when the network is not healthy" - errMakeDhKeys = "failed to generate DH keys (%s for %s): %+v" - errMakeIDs = "failed to generate IDs (%s for %s): %+v" - errAddFingerprint = "failed to add fingerprint %d of %d: %+v (%s for %s)" - errSendRequest = "failed to send %s request to %s: %+v" + errPayloadSize = "size of payload %d exceeds the maximum size of %d (%s for %s)" + errNetworkHealth = "cannot send singe-use request when the network is not healthy" + errMakeDhKeys = "failed to generate DH keys (%s for %s): %+v" + errMakeIDs = "failed to generate IDs (%s for %s): %+v" + errAddFingerprint = "failed to add fingerprint %d of %d: %+v (%s for %s)" + errSendRequest = "failed to send %s request to %s: %+v" + errSendRequestPart = "failed to send request part %d of %d (%s for %s): %+v" // generateDhKeys errGenerateInGroup = "failed to generate private key in group: %+v" @@ -61,11 +64,17 @@ const ( errResponseTimeout = "waiting for response to single-use request timed out after %s" ) +// Maximum number of request part cMix messages. +const maxNumRequestParts = 255 + // GetMaxRequestSize returns the maximum size of a request payload. -func GetMaxRequestSize(net cmix.Client, e2eGrp *cyclic.Group) int { +func GetMaxRequestSize(net CMix, e2eGrp *cyclic.Group) int { payloadSize := message.GetRequestPayloadSize(net.GetMaxMessageLength(), e2eGrp.GetP().ByteLen()) - return message.GetRequestContentsSize(payloadSize) + requestSize := message.GetRequestContentsSize(payloadSize) + requestPartSize := message.GetRequestPartContentsSize( + net.GetMaxMessageLength()) + return requestSize + (maxNumRequestParts * requestPartSize) } /* Single is a system which allows for an end-to-end encrypted anonymous request @@ -81,16 +90,24 @@ func GetMaxRequestSize(net cmix.Client, e2eGrp *cyclic.Group) int { // containing the given payload. The request is identified as coming from a new // user ID and the recipient of the request responds to that address. As a // result, this request does not reveal the identity of the sender. -// The current implementation only allows for a single cMix request payload. -// Because the request payload itself must include negotiation materials, it is -// limited to just a few thousand bits of payload, and will return an error if -// the payload is too large. GetMaxRequestSize can be used to get this max size. +// +// The current implementation allows for up to maxNumRequestParts cMix request +// payloads. GetMaxRequestSize can be used to get the max size. +// // The network follower must be running and healthy to transmit. func TransmitRequest(recipient contact.Contact, tag string, payload []byte, - callback Response, param RequestParams, net cmix.Client, rng csprng.Source, - e2eGrp *cyclic.Group) (id.Round, receptionID.EphemeralIdentity, error) { + callback Response, param RequestParams, net CMix, rng csprng.Source, + e2eGrp *cyclic.Group) ([]id.Round, receptionID.EphemeralIdentity, error) { + + if len(payload) > GetMaxRequestSize(net, e2eGrp) { + return nil, receptionID.EphemeralIdentity{}, errors.Errorf( + errPayloadSize, len(payload), GetMaxRequestSize(net, e2eGrp), tag, + recipient) + } + if !net.IsHealthy() { - return 0, receptionID.EphemeralIdentity{}, errors.New(errNetworkHealth) + return nil, receptionID.EphemeralIdentity{}, + errors.New(errNetworkHealth) } // Get address ID address space size; this blocks until the address space @@ -101,41 +118,49 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, // Generate DH key and public key dhKey, publicKey, err := generateDhKeys(e2eGrp, recipient.DhPubKey, rng) if err != nil { - return 0, receptionID.EphemeralIdentity{}, + return nil, receptionID.EphemeralIdentity{}, errors.Errorf(errMakeDhKeys, tag, recipient, err) } // Build the message payload + payloadSize := message.GetRequestPayloadSize(net.GetMaxMessageLength(), + e2eGrp.GetP().ByteLen()) + firstPart, parts := partitionPayload( + message.GetRequestContentsSize(payloadSize), + message.GetRequestPartContentsSize(net.GetMaxMessageLength()), + payload) request := message.NewRequest( net.GetMaxMessageLength(), e2eGrp.GetP().ByteLen()) requestPayload := message.NewRequestPayload( - request.GetPayloadSize(), payload, param.MaxResponseMessages) + request.GetPayloadSize(), firstPart, param.MaxResponseMessages) // Generate new user ID and address ID var sendingID receptionID.EphemeralIdentity requestPayload, sendingID, err = makeIDs( requestPayload, publicKey, addressSize, param.Timeout, timeStart, rng) if err != nil { - return 0, receptionID.EphemeralIdentity{}, + return nil, receptionID.EphemeralIdentity{}, errors.Errorf(errMakeIDs, tag, recipient, err) } // Encrypt and assemble payload - fp := singleUse.NewTransmitFingerprint(recipient.DhPubKey) - key := singleUse.NewTransmitKey(dhKey) + fp := singleUse.NewRequestFingerprint(recipient.DhPubKey) + key := singleUse.NewRequestKey(dhKey) + encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal()) + // Generate CMix message MAC + mac := singleUse.MakeMAC(key, encryptedPayload) + + // Assemble the payload request.SetPubKey(publicKey) request.SetPayload(encryptedPayload) - // Generate cMix message MAC - mac := singleUse.MakeMAC(key, encryptedPayload) - // Register the response pickup collator := message.NewCollator(param.MaxResponseMessages) timeoutKillChan := make(chan bool) var callbackOnce sync.Once wrapper := func(payload []byte, receptionID receptionID.EphemeralIdentity, - round rounds.Round, err error) { + rounds []rounds.Round, err error) { select { case timeoutKillChan <- true: default: @@ -143,12 +168,14 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, callbackOnce.Do(func() { net.DeleteClientFingerprints(sendingID.Source) - go callback.Callback(payload, receptionID, round, err) + go callback.Callback(payload, receptionID, rounds, err) }) } - cyphers := makeCyphers(dhKey, param.MaxResponseMessages) + cyphers := makeCyphers(dhKey, param.MaxResponseMessages, + singleUse.NewResponseKey, singleUse.NewResponseFingerprint) + roundIds := newRoundIdCollector(len(cyphers)) for i, cy := range cyphers { processor := responseProcessor{ sendingID: sendingID, @@ -157,12 +184,13 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, cy: cy, tag: tag, recipient: &recipient, + roundIDs: roundIds, } err = net.AddFingerprint( sendingID.Source, processor.cy.GetFingerprint(), &processor) if err != nil { - return 0, receptionID.EphemeralIdentity{}, errors.Errorf( + return nil, receptionID.EphemeralIdentity{}, errors.Errorf( errAddFingerprint, i, len(cyphers), tag, recipient, err) } } @@ -177,17 +205,39 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte, } param.CmixParam.Timeout = param.Timeout - rid, _, err := net.Send(recipient.ID, cmixMsg.RandomFingerprint(rng), svc, + rid, _, err := net.Send(recipient.ID, fp, svc, request.Marshal(), mac, param.CmixParam) if err != nil { - return 0, receptionID.EphemeralIdentity{}, + return nil, receptionID.EphemeralIdentity{}, errors.Errorf(errSendRequest, tag, recipient, err) } + // todo: this is jono's work but there's a send above it, + // probably just WIP code, talk to jono and resolve once tests work + roundIDs := make([]id.Round, len(parts)+1) + roundIDs[0] = rid + for i, part := range parts { + requestPart := message.NewRequestPart(net.GetMaxMessageLength()) + requestPart.SetPartNum(uint8(i + 1)) + requestPart.SetContents(part) + + key = singleUse.NewResponseKey(dhKey, uint64(i+1)) + fp = singleUse.NewResponseFingerprint(dhKey, uint64(i+1)) + encryptedPayload = auth.Crypt(key, fp[:24], requestPart.Marshal()) + mac = singleUse.MakeMAC(key, encryptedPayload) + + roundIDs[i+1], _, err = net.Send( + recipient.ID, fp, svc, encryptedPayload, mac, param.CmixParam) + if err != nil { + return nil, receptionID.EphemeralIdentity{}, errors.Errorf( + errSendRequestPart, i, len(part), tag, recipient, err) + } + } + remainingTimeout := param.Timeout - netTime.Since(timeStart) go waitForTimeout(timeoutKillChan, wrapper, remainingTimeout) - return rid, sendingID, nil + return []id.Round{rid}, sendingID, nil } // generateDhKeys generates a new public key and DH key. @@ -201,12 +251,11 @@ func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int, rng io.Reader) ( return nil, nil, errors.Errorf(errGenerateInGroup, err) } privKey := grp.NewIntFromBytes(privKeyBytes) - // Generate public key and DH key publicKey = grp.ExpG(privKey, grp.NewInt(1)) dhKey = grp.Exp(dhPubKey, privKey, grp.NewInt(1)) - return dhKey, publicKey, nil + return } // makeIDs generates a new user ID and address ID with a start and end within @@ -265,8 +314,36 @@ func waitForTimeout(kill chan bool, cb callbackWrapper, timeout time.Duration) { cb( nil, receptionID.EphemeralIdentity{}, - rounds.Round{}, + nil, errors.Errorf(errResponseTimeout, timeout), ) } } + +// partitionPayload splits the payload into its parts. The first part is of size +// firstPartSize and is shorter than the rest since it is sent in the +// message.Request, which includes extra information. It is also returned on its +// own so that it can be handled on its own. The rest of the parts are of size +// partSize and will be sent as part of message.RequestPart. +func partitionPayload(firstPartSize, partSize int, payload []byte) ( + firstPart []byte, parts [][]byte) { + + // Return just the first part if it fits in a single message + if len(payload) <= firstPartSize { + return payload, nil + } + + firstPart = payload[:firstPartSize] + + numParts := (len(payload[:firstPartSize]) + partSize - 1) / partSize + parts = make([][]byte, 0, numParts) + buff := bytes.NewBuffer(payload[firstPartSize:]) + + for n := buff.Next(partSize); len(n) > 0; n = buff.Next(partSize) { + newPart := make([]byte, partSize) + copy(newPart, n) + parts = append(parts, newPart) + } + + return firstPart, parts +} diff --git a/single/requestPartProcessor.go b/single/requestPartProcessor.go new file mode 100644 index 0000000000000000000000000000000000000000..b6564d1e8712741b01a6ff675372455c6c5307a0 --- /dev/null +++ b/single/requestPartProcessor.go @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single/message" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/primitives/id" +) + +const requestPartProcessorName = "requestPartProcessor" + +// requestPartProcessor handles the decryption and collation of request parts. +type requestPartProcessor struct { + myId *id.ID + tag string + cb func(payloadContents []byte, rounds []rounds.Round) + c *message.Collator + cy cypher + roundIDs *roundCollector +} + +func (rpp *requestPartProcessor) Process(msg format.Message, + _ receptionID.EphemeralIdentity, round rounds.Round) { + + decrypted, err := rpp.cy.Decrypt(msg.GetContents(), msg.GetMac()) + if err != nil { + jww.ERROR.Printf("[SU] Failed to decrypt single-use request payload "+ + "for %s to %s: %+v", rpp.tag, rpp.myId, err) + return + } + + requestPart, err := message.UnmarshalRequestPart(decrypted) + if err != nil { + jww.ERROR.Printf("[SU] Failed to unmarshal single-use request part "+ + "payload for %s to %s: %+v", rpp.tag, rpp.myId, err) + return + } + + payload, done, err := rpp.c.Collate(requestPart) + if err != nil { + jww.ERROR.Printf("[SU] Failed to collate single-use request payload "+ + "for %s to %s: %+v", rpp.tag, rpp.myId, err) + return + } + + rpp.roundIDs.add(round) + + if done { + rpp.cb(payload, rpp.roundIDs.getList()) + } +} + +func (rpp *requestPartProcessor) String() string { + return requestPartProcessorName +} diff --git a/single/responseProcessor.go b/single/responseProcessor.go index 62f526a4919d115b60376be3ad565070c68ad63d..dac81e10cf77ad0bbaa98ad12953fa2746a05007 100644 --- a/single/responseProcessor.go +++ b/single/responseProcessor.go @@ -11,8 +11,10 @@ import ( "gitlab.com/elixxir/primitives/format" ) +const responseProcessorName = "responseProcessorName" + type callbackWrapper func(payload []byte, - receptionID receptionID.EphemeralIdentity, round rounds.Round, err error) + receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) // responseProcessor is registered for each potential fingerprint. Adheres to // the message.Processor interface registered with cmix.Client @@ -23,6 +25,7 @@ type responseProcessor struct { cy cypher tag string recipient *contact.Contact + roundIDs *roundCollector } // Process decrypts a response part and adds it to the collator - returning @@ -38,7 +41,14 @@ func (rsp *responseProcessor) Process(ecrMsg format.Message, return } - payload, done, err := rsp.c.Collate(decrypted) + responsePart, err := message.UnmarshalResponsePart(decrypted) + if err != nil { + jww.ERROR.Printf("[SU] Failed to unmarshal single-use response part "+ + "payload for %s to %s: %+v", rsp.tag, rsp.recipient.ID, err) + return + } + + payload, done, err := rsp.c.Collate(responsePart) if err != nil { jww.ERROR.Printf("[SU] Failed to collate single-use response payload "+ "for %s to %s, single use may fail: %+v", @@ -46,8 +56,10 @@ func (rsp *responseProcessor) Process(ecrMsg format.Message, return } + rsp.roundIDs.add(round) + if done { - rsp.callback(payload, receptionID, round, nil) + rsp.callback(payload, receptionID, rsp.roundIDs.getList(), nil) } } diff --git a/single/roundCollector.go b/single/roundCollector.go new file mode 100644 index 0000000000000000000000000000000000000000..4d48b8e32c0f3e458457502481dd69344c510ee1 --- /dev/null +++ b/single/roundCollector.go @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package single + +import ( + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/xx_network/primitives/id" + "sync" +) + +// roundCollector keeps track of a list of unique rounds. Multiple inserts of +// the same round are ignored. It is multi-thread safe. +type roundCollector struct { + list map[id.Round]rounds.Round + mux sync.Mutex +} + +// newRoundIdCollector initialises a new roundCollector with a list of the +// given size. Size is not necessary and can be larger or smaller than the real +// size. +func newRoundIdCollector(size int) *roundCollector { + return &roundCollector{ + list: make(map[id.Round]rounds.Round, size), + } +} + +// add inserts a new round to the list. +func (rc *roundCollector) add(round rounds.Round) { + rc.mux.Lock() + defer rc.mux.Unlock() + + rc.list[round.ID] = round +} + +// getList returns the list of round IDs. +func (rc *roundCollector) getList() []rounds.Round { + rc.mux.Lock() + defer rc.mux.Unlock() + + list := make([]rounds.Round, 0, len(rc.list)) + + for _, round := range rc.list { + list = append(list, round) + } + + return list +} diff --git a/storage/clientVersion/store_test.go b/storage/clientVersion/store_test.go index c9f5ab78a1f80df2a3388677adc080a42e1ff6e2..0e8cae86f2822b5ea9d3cb09e1bc71cd0c682884 100644 --- a/storage/clientVersion/store_test.go +++ b/storage/clientVersion/store_test.go @@ -19,7 +19,7 @@ import ( // Happy path. func TestNewStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expected := &Store{ version: version.New(42, 43, "44"), kv: kv.Prefix(prefix), @@ -38,7 +38,7 @@ func TestNewStore(t *testing.T) { // Happy path. func TestLoadStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) ver := version.New(1, 2, "3A") expected := &Store{ @@ -64,7 +64,7 @@ func TestLoadStore(t *testing.T) { // Error path: an error is returned when the loaded Store has an invalid version // that fails to be parsed. func TestLoadStore_ParseVersionError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) obj := versioned.Object{ Version: storeVersion, Timestamp: netTime.Now(), @@ -85,7 +85,7 @@ func TestLoadStore_ParseVersionError(t *testing.T) { // Happy path. func TestStore_Get(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expected := version.New(1, 2, "3A") s := &Store{ @@ -102,7 +102,7 @@ func TestStore_Get(t *testing.T) { // Happy path. func TestStore_CheckUpdateRequired(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) storedVersion := version.New(1, 2, "3") newVersion := version.New(2, 3, "4") s, err := NewStore(storedVersion, kv) @@ -129,7 +129,7 @@ func TestStore_CheckUpdateRequired(t *testing.T) { // Happy path: the new version is equal to the stored version. func TestStore_CheckUpdateRequired_EqualVersions(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) storedVersion := version.New(2, 3, "3") newVersion := version.New(2, 3, "4") s, err := NewStore(storedVersion, kv) @@ -156,7 +156,7 @@ func TestStore_CheckUpdateRequired_EqualVersions(t *testing.T) { // Error path: new version is older than stored version. func TestStore_CheckUpdateRequired_NewVersionTooOldError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) storedVersion := version.New(2, 3, "4") newVersion := version.New(1, 2, "3") s, err := NewStore(storedVersion, kv) @@ -184,7 +184,7 @@ func TestStore_CheckUpdateRequired_NewVersionTooOldError(t *testing.T) { // Happy path. func TestStore_update(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) ver1 := version.New(1, 2, "3A") ver2 := version.New(1, 5, "patch5") @@ -206,7 +206,7 @@ func TestStore_update(t *testing.T) { // Happy path. func TestStore_save(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) ver := version.New(1, 2, "3A") s := &Store{ diff --git a/storage/user/cryptographic_test.go b/storage/user/cryptographic_test.go index 0d2fab2084c773f83053760fccb8dcb5ca794b2d..1096ccf683e288ae99577bc14989ab3fd71af599 100644 --- a/storage/user/cryptographic_test.go +++ b/storage/user/cryptographic_test.go @@ -19,7 +19,7 @@ import ( // Test for NewCryptographicIdentity function func TestNewCryptographicIdentity(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) salt := []byte("salt") _ = newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv) @@ -32,7 +32,7 @@ func TestNewCryptographicIdentity(t *testing.T) { // Test loading cryptographic identity from KV store func TestLoadCryptographicIdentity(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) salt := []byte("salt") ci := newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv) @@ -53,7 +53,7 @@ func TestLoadCryptographicIdentity(t *testing.T) { // Happy path for GetReceptionRSA function func TestCryptographicIdentity_GetReceptionRSA(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) pk1, err := rsa.GenerateKey(rand.Reader, 64) if err != nil { @@ -72,7 +72,7 @@ func TestCryptographicIdentity_GetReceptionRSA(t *testing.T) { // Happy path for GetTransmissionRSA function func TestCryptographicIdentity_GetTransmissionRSA(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) pk1, err := rsa.GenerateKey(rand.Reader, 64) if err != nil { @@ -91,7 +91,7 @@ func TestCryptographicIdentity_GetTransmissionRSA(t *testing.T) { // Happy path for GetSalt function func TestCryptographicIdentity_GetTransmissionSalt(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) ts := []byte("transmission salt") rs := []byte("reception salt") @@ -103,7 +103,7 @@ func TestCryptographicIdentity_GetTransmissionSalt(t *testing.T) { // Happy path for GetSalt function func TestCryptographicIdentity_GetReceptionSalt(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) ts := []byte("transmission salt") rs := []byte("reception salt") @@ -115,7 +115,7 @@ func TestCryptographicIdentity_GetReceptionSalt(t *testing.T) { // Happy path for GetUserID function func TestCryptographicIdentity_GetTransmissionID(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) rid := id.NewIdFromString("zezima", id.User, t) tid := id.NewIdFromString("jakexx360", id.User, t) salt := []byte("salt") @@ -127,7 +127,7 @@ func TestCryptographicIdentity_GetTransmissionID(t *testing.T) { // Happy path for GetUserID function func TestCryptographicIdentity_GetReceptionID(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) rid := id.NewIdFromString("zezima", id.User, t) tid := id.NewIdFromString("jakexx360", id.User, t) salt := []byte("salt") @@ -139,7 +139,7 @@ func TestCryptographicIdentity_GetReceptionID(t *testing.T) { // Happy path for IsPrecanned functions func TestCryptographicIdentity_IsPrecanned(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("zezima", id.User, t) salt := []byte("salt") ci := newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, true, kv) diff --git a/storage/user/registation_test.go b/storage/user/registation_test.go index 31c6d3d1ee67f4e81fddd4a5822a18d0ad4d7ccf..cec369ea41ca74c63ea972e3455a4b577ebcd464 100644 --- a/storage/user/registation_test.go +++ b/storage/user/registation_test.go @@ -21,7 +21,7 @@ import ( // Test User GetRegistrationValidationSignature function func TestUser_GetRegistrationValidationSignature(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) @@ -56,7 +56,7 @@ func TestUser_GetRegistrationValidationSignature(t *testing.T) { // Test SetRegistrationValidationSignature setter func TestUser_SetRegistrationValidationSignature(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) @@ -99,7 +99,7 @@ func TestUser_SetRegistrationValidationSignature(t *testing.T) { // Test loading registrationValidationSignature from the KV store func TestUser_loadRegistrationValidationSignature(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) @@ -142,7 +142,7 @@ func TestUser_loadRegistrationValidationSignature(t *testing.T) { // Test User's getter/setter functions for TimeStamp func TestUser_GetRegistrationTimestamp(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) @@ -191,7 +191,7 @@ func TestUser_GetRegistrationTimestamp(t *testing.T) { // Test loading registrationTimestamp from the KV store func TestUser_loadRegistrationTimestamp(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) diff --git a/storage/user/user_test.go b/storage/user/user_test.go index 0fd3d8d1819163dcadac0d7bed29f183f45acd32..01a93088c1e54c7d2b6d03a6d5a6a1cb0cfb8803 100644 --- a/storage/user/user_test.go +++ b/storage/user/user_test.go @@ -8,7 +8,6 @@ package user import ( - "bytes" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/ekv" "gitlab.com/xx_network/crypto/signature/rsa" @@ -18,7 +17,7 @@ import ( // Test loading user from a KV store func TestLoadUser(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) _, err := LoadUser(kv) if err == nil { @@ -41,7 +40,7 @@ func TestLoadUser(t *testing.T) { // Test NewUser function func TestNewUser(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) uid := id.NewIdFromString("test", id.User, t) salt := []byte("salt") u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) @@ -49,20 +48,3 @@ func TestNewUser(t *testing.T) { t.Errorf("Failed to create new user: %+v", err) } } - -// Test GetCryptographicIdentity function from user -func TestUser_GetCryptographicIdentity(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) - uid := id.NewIdFromString("test", id.User, t) - rsalt := []byte("reception salt") - tsalt := []byte("transmission salt") - u, err := NewUser(kv, uid, uid, tsalt, rsalt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false) - if err != nil || u == nil { - t.Errorf("Failed to create new user: %+v", err) - } - - ci := u.GetCryptographicIdentity() - if bytes.Compare(ci.transmissionSalt, tsalt) != 0 { - t.Errorf("Cryptographic Identity not retrieved properly") - } -} diff --git a/storage/user/username_test.go b/storage/user/username_test.go index 7571d3804136b560f5730b76d69b5672cd6d5c48..f4a851145b8296bdab49b07bb6996d51344dc2ae 100644 --- a/storage/user/username_test.go +++ b/storage/user/username_test.go @@ -18,7 +18,7 @@ import ( // Test normal function and errors for User's SetUsername function func TestUser_SetUsername(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) tid := id.NewIdFromString("trans", id.User, t) rid := id.NewIdFromString("recv", id.User, t) tsalt := []byte("tsalt") @@ -52,7 +52,7 @@ func TestUser_SetUsername(t *testing.T) { // Test functionality of User's GetUsername function func TestUser_GetUsername(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) tid := id.NewIdFromString("trans", id.User, t) rid := id.NewIdFromString("recv", id.User, t) tsalt := []byte("tsalt") @@ -80,7 +80,7 @@ func TestUser_GetUsername(t *testing.T) { // Test the loadUsername helper function func TestUser_loadUsername(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) tid := id.NewIdFromString("trans", id.User, t) rid := id.NewIdFromString("recv", id.User, t) tsalt := []byte("tsalt") diff --git a/storage/utility/blockStore_test.go b/storage/utility/blockStore_test.go index c9e2fb56daef0cff51eb7e283b463f9782ac4cf6..3f13417858d38cb5d7b94667f17c0d17535bf29b 100644 --- a/storage/utility/blockStore_test.go +++ b/storage/utility/blockStore_test.go @@ -33,7 +33,7 @@ func TestNewBlockStore(t *testing.T) { blockSize: 20, firstSaved: 0, lastSaved: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } bs, err := NewBlockStore(expected.numBlocks, expected.blockSize, expected.kv) @@ -75,7 +75,7 @@ func TestBlockStore_Store_LoadBlockStore(t *testing.T) { expected := make([][]byte, len(iter[v.dataCutIndex:])) copy(expected, iter[v.dataCutIndex:]) - bs, err := NewBlockStore(v.numBlocks, v.blockSize, versioned.NewKV(make(ekv.Memstore))) + bs, err := NewBlockStore(v.numBlocks, v.blockSize, versioned.NewKV(ekv.MakeMemstore())) if err != nil { t.Errorf("Failed to create new BlockStore (%d): %+v", i, err) } @@ -125,7 +125,7 @@ func TestBlockStore_saveBlock_loadBlock(t *testing.T) { blockSize: 20, firstSaved: 0, lastSaved: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } for i := range bs.block { @@ -159,7 +159,7 @@ func TestBlockStore_saveBlock_SaveError(t *testing.T) { blockSize: 20, firstSaved: 0, lastSaved: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } for i := range bs.block { @@ -187,7 +187,7 @@ func TestBlockStore_saveBlock_SaveError(t *testing.T) { // Error path: loading of nonexistent key returns an error. func TestBlockStore_loadBlock_LoadStorageError(t *testing.T) { expectedErr := strings.SplitN(bKvLoadErr, "%", 2)[0] - bs := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + bs := &BlockStore{kv: versioned.NewKV(ekv.MakeMemstore())} _, err := bs.loadBlock(0) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("loadBlock() did not return the expected error."+ @@ -197,7 +197,7 @@ func TestBlockStore_loadBlock_LoadStorageError(t *testing.T) { // Error path: unmarshalling of invalid data fails. func TestBlockStore_loadBlock_UnmarshalError(t *testing.T) { - bs := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + bs := &BlockStore{kv: versioned.NewKV(ekv.MakeMemstore())} expectedErr := strings.SplitN(bJsonUnmarshalErr, "%", 2)[0] // Construct object with invalid data @@ -228,7 +228,7 @@ func TestBlockStore_pruneBlocks(t *testing.T) { blockSize: 32, firstSaved: 0, lastSaved: 0, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } // Save blocks to storage @@ -293,7 +293,7 @@ func TestBlockStore_getKey_Consistency(t *testing.T) { func TestBlockStore_save_load(t *testing.T) { bs := &BlockStore{ numBlocks: 5, blockSize: 6, firstSaved: 7, lastSaved: 8, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } err := bs.save() @@ -317,7 +317,7 @@ func TestBlockStore_save_load(t *testing.T) { func TestBlockStore_load_KvGetError(t *testing.T) { expectedErr := strings.SplitN(bsKvLoadErr, "%", 2)[0] - testBS := &BlockStore{kv: versioned.NewKV(make(ekv.Memstore))} + testBS := &BlockStore{kv: versioned.NewKV(ekv.MakeMemstore())} err := testBS.load() if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("load() did not return an error for a nonexistent item in storage."+ @@ -328,7 +328,7 @@ func TestBlockStore_load_KvGetError(t *testing.T) { // Error path: unmarshalling of invalid data fails. func TestBlockStore_load_UnmarshalError(t *testing.T) { expectedErr := strings.SplitN(bsKvUnmarshalErr, "%", 2)[0] - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) // Construct invalid versioning object obj := versioned.Object{ diff --git a/storage/utility/bucketParams_test.go b/storage/utility/bucketParams_test.go index c2ce1bdbfdb99bb0dd0dba059216556805cb03bc..d908ac99ad964b35a345fbfde6af92c642b072c4 100644 --- a/storage/utility/bucketParams_test.go +++ b/storage/utility/bucketParams_test.go @@ -18,7 +18,7 @@ import ( // todo: write tests func TestNewBucketParamsStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) capacity, leakedTokens, leakDuration := uint32(10), uint32(11), time.Duration(12) bps, err := NewBucketParamsStore(capacity, leakedTokens, leakDuration, kv) if err != nil { @@ -50,7 +50,7 @@ func TestNewBucketParamsStore(t *testing.T) { } func TestLoadBucketParamsStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) capacity, leakedTokens, leakDuration := uint32(10), uint32(11), time.Duration(12) bps, err := NewBucketParamsStore(capacity, leakedTokens, leakDuration, kv) if err != nil { diff --git a/storage/utility/dh_test.go b/storage/utility/dh_test.go index 36fb3c5734af6ea7bd29479279f01b10522b803d..bea2f2448d14a80c3319871f78edf7f1edad90e9 100644 --- a/storage/utility/dh_test.go +++ b/storage/utility/dh_test.go @@ -15,7 +15,7 @@ import ( // Unit test for StoreCyclicKey func TestStoreCyclicKey(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) grp := getTestGroup() x := grp.NewInt(77) @@ -28,7 +28,7 @@ func TestStoreCyclicKey(t *testing.T) { // Unit test for LoadCyclicKey func TestLoadCyclicKey(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) grp := getTestGroup() x := grp.NewInt(77) @@ -50,7 +50,7 @@ func TestLoadCyclicKey(t *testing.T) { // Unit test for DeleteCyclicKey func TestDeleteCyclicKey(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) grp := getTestGroup() x := grp.NewInt(77) diff --git a/storage/utility/group_test.go b/storage/utility/group_test.go index ef8ed3ea22dead0c1764d094513a22ccf53e931f..02e6504be687d1e7b331ad63d6c139d9d6963f22 100644 --- a/storage/utility/group_test.go +++ b/storage/utility/group_test.go @@ -17,7 +17,7 @@ import ( // Unit test for StoreGroup func TestStoreGroup(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) grp := getTestGroup() err := StoreGroup(vkv, grp, "testKey") @@ -28,7 +28,7 @@ func TestStoreGroup(t *testing.T) { // Unit test for LoadGroup func TestLoadGroup(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) grp := getTestGroup() diff --git a/storage/utility/messageBuffer_test.go b/storage/utility/messageBuffer_test.go index e9d1a841b199024be981abc511a78d5355521f76..fc7f39a7c8fba2acdb6f8eb2efe395afcc7ce814 100644 --- a/storage/utility/messageBuffer_test.go +++ b/storage/utility/messageBuffer_test.go @@ -70,7 +70,7 @@ func TestNewMessageBuffer(t *testing.T) { messages: make(map[MessageHash]struct{}), processingMessages: make(map[MessageHash]struct{}), handler: th, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), key: "testKey", } @@ -94,7 +94,7 @@ func TestLoadMessageBuffer(t *testing.T) { messages: make(map[MessageHash]struct{}), processingMessages: make(map[MessageHash]struct{}), handler: th, - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), key: "testKey", } _ = addTestMessages(expectedMB, 20) @@ -124,7 +124,7 @@ func TestLoadMessageBuffer(t *testing.T) { // Tests happy path of save() with a new empty MessageBuffer. func TestMessageBuffer_save_NewMB(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) key := "testKey" mb, err := NewMessageBuffer(kv, newTestHandler(), key) @@ -154,7 +154,7 @@ func TestMessageBuffer_save_NewMB(t *testing.T) { // Tests happy path of save(). func TestMessageBuffer_save(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) key := "testKey" mb, err := NewMessageBuffer(kv, newTestHandler(), key) if err != nil { @@ -186,7 +186,7 @@ func TestMessageBuffer_save(t *testing.T) { // Tests happy path of MessageBuffer.Add(). func TestMessageBuffer_Add(t *testing.T) { // Create new MessageBuffer and fill with messages - testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), newTestHandler(), "testKey") + testMB, err := NewMessageBuffer(versioned.NewKV(ekv.MakeMemstore()), newTestHandler(), "testKey") if err != nil { t.Fatalf("Failed to create new MessageBuffer: %v", err) } @@ -216,7 +216,7 @@ func TestMessageBuffer_Add(t *testing.T) { // Tests happy path of MessageBuffer.Next(). func TestMessageBuffer_Next(t *testing.T) { // Create new MessageBuffer and fill with messages - testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), newTestHandler(), "testKey") + testMB, err := NewMessageBuffer(versioned.NewKV(ekv.MakeMemstore()), newTestHandler(), "testKey") if err != nil { t.Fatalf("Failed to create new MessageBuffer: %v", err) } @@ -246,7 +246,7 @@ func TestMessageBuffer_Next(t *testing.T) { func TestMessageBuffer_InvalidNext(t *testing.T) { // Create new MessageBuffer and fill with messages - testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), newTestHandler(), "testKey") + testMB, err := NewMessageBuffer(versioned.NewKV(ekv.MakeMemstore()), newTestHandler(), "testKey") if err != nil { t.Fatalf("Failed to create new MessageBuffer: %v", err) } @@ -267,7 +267,7 @@ func TestMessageBuffer_InvalidNext(t *testing.T) { func TestMessageBuffer_Succeeded(t *testing.T) { th := newTestHandler() // Create new MessageBuffer and fill with message - testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), th, "testKey") + testMB, err := NewMessageBuffer(versioned.NewKV(ekv.MakeMemstore()), th, "testKey") if err != nil { t.Fatalf("Failed to create new MessageBuffer: %v", err) } @@ -293,7 +293,7 @@ func TestMessageBuffer_Succeeded(t *testing.T) { func TestMessageBuffer_Failed(t *testing.T) { th := newTestHandler() // Create new MessageBuffer and fill with message - testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), th, "testKey") + testMB, err := NewMessageBuffer(versioned.NewKV(ekv.MakeMemstore()), th, "testKey") if err != nil { t.Fatalf("Failed to create new MessageBuffer: %v", err) } diff --git a/storage/utility/multiStateVector_test.go b/storage/utility/multiStateVector_test.go index 83527da507a2f3f9641f0625192faca0fbedd1c7..38085804b5bf11adbf35e21d240863b84b2a2a0c 100644 --- a/storage/utility/multiStateVector_test.go +++ b/storage/utility/multiStateVector_test.go @@ -25,7 +25,7 @@ import ( // Tests that NewMultiStateVector returns a new MultiStateVector with the // expected values. func TestNewMultiStateVector(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) key := "testKey" expected := &MultiStateVector{ numKeys: 189, @@ -98,7 +98,7 @@ func TestNewMultiStateVector_StateMapError(t *testing.T) { // random states are generated and manually inserted into the vector and then // each key is checked for the expected vector func TestMultiStateVector_Get(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -164,7 +164,7 @@ func TestMultiStateVector_Get(t *testing.T) { // Error path: tests that MultiStateVector.get returns the expected error when // the key number is greater than the max key number. func TestMultiStateVector_get_KeyNumMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -190,7 +190,7 @@ func TestMultiStateVector_Set(t *testing.T) { {false, false, true, false, true}, {false, false, false, false, false}, } - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, stateMap, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -243,7 +243,7 @@ func TestMultiStateVector_Set(t *testing.T) { // Error path: tests that MultiStateVector.Set returns the expected error when // the key number is greater than the last key number. func TestMultiStateVector_Set_KeyNumMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -262,7 +262,7 @@ func TestMultiStateVector_Set_KeyNumMaxError(t *testing.T) { // Tests that MultiStateVector.SetMany func TestMultiStateVector_SetMany(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -301,7 +301,7 @@ func TestMultiStateVector_SetMany(t *testing.T) { // Error path: tests that MultiStateVector.SetMany returns the expected error // when one of the keys is greater than the last key number. func TestMultiStateVector_SetMany_KeyNumMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -321,7 +321,7 @@ func TestMultiStateVector_SetMany_KeyNumMaxError(t *testing.T) { // Error path: tests that MultiStateVector.set returns the expected error when // the key number is greater than the last key number. func TestMultiStateVector_set_KeyNumMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -340,7 +340,7 @@ func TestMultiStateVector_set_KeyNumMaxError(t *testing.T) { // Error path: tests that MultiStateVector.set returns the expected error when // the given state is greater than the last state. func TestMultiStateVector_set_NewStateMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -359,7 +359,7 @@ func TestMultiStateVector_set_NewStateMaxError(t *testing.T) { // Error path: tests that MultiStateVector.set returns the expected error when // the state read from the vector is greater than the last state. func TestMultiStateVector_set_OldStateMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -381,7 +381,7 @@ func TestMultiStateVector_set_OldStateMaxError(t *testing.T) { // the state change is not allowed by the state map. func TestMultiStateVector_set_StateChangeError(t *testing.T) { stateMap := [][]bool{{true, false}, {true, true}} - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 2, stateMap, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -401,7 +401,7 @@ func TestMultiStateVector_set_StateChangeError(t *testing.T) { // Tests that MultiStateVector.GetNumKeys returns the expected number of keys. func TestMultiStateVector_GetNumKeys(t *testing.T) { numKeys := uint16(155) - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(numKeys, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -416,7 +416,7 @@ func TestMultiStateVector_GetNumKeys(t *testing.T) { // Tests that MultiStateVector.GetCount returns the correct count for each state // after each key has been set to a random state. func TestMultiStateVector_GetCount(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector( 156, 5, nil, "TestMultiStateVector_GetCount", kv) if err != nil { @@ -452,7 +452,7 @@ func TestMultiStateVector_GetCount(t *testing.T) { // Error path: tests that MultiStateVector.GetCount returns the expected error // when the given state is greater than the last state. func TestMultiStateVector_GetCount_NewStateMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -471,7 +471,7 @@ func TestMultiStateVector_GetCount_NewStateMaxError(t *testing.T) { // Tests that MultiStateVector.GetKeys returns the correct list of keys for each // state after each key has been set to a random state. func TestMultiStateVector_GetKeys(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector( 156, 5, nil, "TestMultiStateVector_GetKeys", kv) if err != nil { @@ -507,7 +507,7 @@ func TestMultiStateVector_GetKeys(t *testing.T) { // Error path: tests that MultiStateVector.GetKeys returns the expected error // when the given state is greater than the last state. func TestMultiStateVector_GetKeys_NewStateMaxError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -533,7 +533,7 @@ func TestMultiStateVector_DeepCopy(t *testing.T) { {false, false, true, false, true}, {false, false, false, false, false}, } - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, stateMap, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -597,7 +597,7 @@ func TestMultiStateVector_DeepCopy(t *testing.T) { // Tests that MultiStateVector.DeepCopy is able to make the expected copy when // the state map is nil. func TestMultiStateVector_DeepCopy_NilStateMap(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(155, 5, nil, "", kv) if err != nil { t.Errorf("Failed to create new MultiStateVector: %+v", err) @@ -871,7 +871,7 @@ func TestLoadMultiStateVector(t *testing.T) { // no object is saved in storage. func TestLoadMultiStateVector_GetFromStorageError(t *testing.T) { key := "TestLoadMultiStateVector_GetFromStorageError" - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedErr := strings.Split(loadGetMsvErr, "%")[0] _, err := LoadMultiStateVector(nil, key, kv) @@ -885,7 +885,7 @@ func TestLoadMultiStateVector_GetFromStorageError(t *testing.T) { // Error path: tests that LoadMultiStateVector returns the expected error when // the data in storage cannot be unmarshalled. func TestLoadMultiStateVector_UnmarshalError(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) key := "TestLoadMultiStateVector_MarshalError" expectedErr := strings.Split(loadUnmarshalMsvErr, "%")[0] @@ -920,7 +920,7 @@ func TestMultiStateVector_save(t *testing.T) { vect: []uint64{0, 1, 2}, stateUseCount: []uint16{5, 12, 104, 0, 4000}, key: makeStateVectorKey("TestMultiStateVector_save"), - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } expectedData, err := msv.marshal() @@ -1026,7 +1026,7 @@ func Test_makeMultiStateVectorKey(t *testing.T) { // random state. func newTestFilledMSV(numKeys uint16, numStates uint8, stateMap [][]bool, key string, t *testing.T) (*MultiStateVector, *versioned.KV) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) msv, err := NewMultiStateVector(numKeys, numStates, stateMap, key, kv) if err != nil { diff --git a/storage/utility/sidh_test.go b/storage/utility/sidh_test.go index 34ab3c43cfa84ee85de99fa3dff357e44dec244d..d547011d3ef6860e685204ce013dd437675215e5 100644 --- a/storage/utility/sidh_test.go +++ b/storage/utility/sidh_test.go @@ -19,7 +19,7 @@ import ( // TestStoreLoadDeleteSIDHPublicKey tests the load/store/delete functions // for SIDH Public Keys func TestStoreLoadDeleteSIDHPublicKey(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) rng := fastRNG.NewStreamGenerator(1, 3, csprng.NewSystemRNG) myRng := rng.GetStream() @@ -85,7 +85,7 @@ func TestStoreLoadDeleteSIDHPublicKey(t *testing.T) { // TestStoreLoadDeleteSIDHPublicKey tests the load/store/delete functions // for SIDH Private Keys func TestStoreLoadDeleteSIDHPrivateKey(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := versioned.NewKV(kv) rng := fastRNG.NewStreamGenerator(1, 3, csprng.NewSystemRNG) myRng := rng.GetStream() diff --git a/storage/utility/stateVector_test.go b/storage/utility/stateVector_test.go index 2d537ef61f2d2a7c810d3e609362cb8551f6a17e..4b2188b25c6dd41689aea9cbe003ea0ba8443b7a 100644 --- a/storage/utility/stateVector_test.go +++ b/storage/utility/stateVector_test.go @@ -23,7 +23,7 @@ import ( // Tests that NewStateVector creates the expected new StateVector and that it is // saved to storage. func TestNewStateVector(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) key := "myTestKey" numKeys := uint32(275) expected := &StateVector{ @@ -559,7 +559,7 @@ func TestLoadStateVector(t *testing.T) { // original. func TestLoadStateVector_GetError(t *testing.T) { key := "StateVectorLoadStateVector" - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedErr := "object not found" _, err := LoadStateVector(kv, key) @@ -574,7 +574,7 @@ func TestLoadStateVector_GetError(t *testing.T) { // original. func TestLoadStateVector_UnmarshalError(t *testing.T) { key := "StateVectorLoadStateVector" - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) // Save invalid StateVector to storage obj := versioned.Object{ @@ -606,7 +606,7 @@ func TestStateVector_save(t *testing.T) { numKeys: 1000, numAvailable: 1000, key: makeStateVectorKey(key), - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } expectedData, err := sv.marshal() if err != nil { @@ -717,7 +717,7 @@ func TestStateVector_SaveTEST(t *testing.T) { numKeys: 1000, numAvailable: 1000, key: makeStateVectorKey(key), - kv: versioned.NewKV(make(ekv.Memstore)), + kv: versioned.NewKV(ekv.MakeMemstore()), } expectedData, err := sv.marshal() if err != nil { @@ -853,7 +853,7 @@ func TestStateVector_SetNumAvailableTEST_InvalidInterfaceError(t *testing.T) { func TestStateVector_SetKvTEST(t *testing.T) { sv := newTestStateVector("SetKvTEST", 1000, t) - kv := versioned.NewKV(make(ekv.Memstore)).Prefix("NewKV") + kv := versioned.NewKV(ekv.MakeMemstore()).Prefix("NewKV") sv.SetKvTEST(kv, t) if sv.kv != kv { @@ -881,7 +881,7 @@ func TestStateVector_SetKvTEST_InvalidInterfaceError(t *testing.T) { // newTestStateVector produces a new StateVector using the specified number of // keys and key string for testing. func newTestStateVector(key string, numKeys uint32, t *testing.T) *StateVector { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) sv, err := NewStateVector(kv, key, numKeys) if err != nil { diff --git a/storage/versioned/kv_test.go b/storage/versioned/kv_test.go index 88cc0fcb8faaffca1886d8dc2b95e6c62c540c58..99bf17a969a0d042b1e1934c07957e00a75f8610 100644 --- a/storage/versioned/kv_test.go +++ b/storage/versioned/kv_test.go @@ -17,7 +17,7 @@ import ( // KV get should call the Upgrade function when it's available func TestVersionedKV_Get_Err(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) key := vkv.GetFullKey("test", 0) result, err := vkv.Get(key, 0) @@ -34,7 +34,7 @@ func TestVersionedKV_Get_Err(t *testing.T) { // Test versioned KV happy path func TestVersionedKV_GetUpgrade(t *testing.T) { // Set up a dummy KV with the required data - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) key := vkv.GetFullKey("test", 0) original := Object{ @@ -42,8 +42,10 @@ func TestVersionedKV_GetUpgrade(t *testing.T) { Timestamp: netTime.Now(), Data: []byte("not upgraded"), } - originalSerialized := original.Marshal() - kv[key] = originalSerialized + err := kv.Set(key, &original) + if err != nil { + t.Errorf("Failed to set original: %+v", err) + } upgrade := []Upgrade{func(oldObject *Object) (*Object, error) { return &Object{ @@ -69,7 +71,7 @@ func TestVersionedKV_GetUpgrade(t *testing.T) { // Test versioned KV key not found path func TestVersionedKV_GetUpgrade_KeyNotFound(t *testing.T) { // Set up a dummy KV with the required data - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) key := "test" @@ -91,7 +93,7 @@ func TestVersionedKV_GetUpgrade_KeyNotFound(t *testing.T) { // Test versioned KV upgrade func returns error path func TestVersionedKV_GetUpgrade_UpgradeReturnsError(t *testing.T) { // Set up a dummy KV with the required data - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) key := vkv.GetFullKey("test", 0) original := Object{ @@ -99,8 +101,10 @@ func TestVersionedKV_GetUpgrade_UpgradeReturnsError(t *testing.T) { Timestamp: netTime.Now(), Data: []byte("not upgraded"), } - originalSerialized := original.Marshal() - kv[key] = originalSerialized + err := kv.Set(key, &original) + if err != nil { + t.Errorf("Failed to set original: %+v", err) + } upgrade := []Upgrade{func(oldObject *Object) (*Object, error) { return &Object{}, errors.New("test error") @@ -119,7 +123,7 @@ func TestVersionedKV_GetUpgrade_UpgradeReturnsError(t *testing.T) { // Test delete key happy path func TestVersionedKV_Delete(t *testing.T) { // Set up a dummy KV with the required data - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) key := vkv.GetFullKey("test", 0) original := Object{ @@ -127,16 +131,20 @@ func TestVersionedKV_Delete(t *testing.T) { Timestamp: netTime.Now(), Data: []byte("not upgraded"), } - originalSerialized := original.Marshal() - kv[key] = originalSerialized + err := kv.Set(key, &original) + if err != nil { + t.Errorf("Failed to set original: %+v", err) + } - err := vkv.Delete("test", 0) + err = vkv.Delete("test", 0) if err != nil { t.Fatalf("Error getting something that should have been in: %v", err) } - if _, ok := kv[key]; ok { + o := &Object{} + err = kv.Get(key, o) + if err == nil { t.Fatal("Key still exists in kv map") } } @@ -144,7 +152,7 @@ func TestVersionedKV_Delete(t *testing.T) { // Test get without Upgrade path func TestVersionedKV_Get(t *testing.T) { // Set up a dummy KV with the required data - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) originalVersion := uint64(0) key := vkv.GetFullKey("test", originalVersion) @@ -153,8 +161,10 @@ func TestVersionedKV_Get(t *testing.T) { Timestamp: netTime.Now(), Data: []byte("not upgraded"), } - originalSerialized := original.Marshal() - kv[key] = originalSerialized + err := kv.Set(key, &original) + if err != nil { + t.Errorf("Failed to set original in kv: %+v", err) + } result, err := vkv.Get("test", originalVersion) if err != nil { @@ -169,7 +179,7 @@ func TestVersionedKV_Get(t *testing.T) { // Test that Set puts data in the store func TestVersionedKV_Set(t *testing.T) { - kv := make(ekv.Memstore) + kv := ekv.MakeMemstore() vkv := NewKV(kv) originalVersion := uint64(1) key := vkv.GetFullKey("test", originalVersion) @@ -184,8 +194,9 @@ func TestVersionedKV_Set(t *testing.T) { } // Store should now have data in it at that key - _, ok := kv[key] - if !ok { - t.Error("data store didn't have anything in the key") + o := &Object{} + err = kv.Get(key, o) + if err != nil { + t.Error("data store didn't have anything in the key: %+v", err) } } diff --git a/ud/addFact.go b/ud/addFact.go index 1985970ff976eeb25d9c437a187287cf61fe7c8a..6412e94b308871e9aa7266b2176e39cb8f169376 100644 --- a/ud/addFact.go +++ b/ud/addFact.go @@ -8,15 +8,10 @@ import ( "gitlab.com/elixxir/crypto/factID" "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/primitives/fact" - "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" ) -type addFactComms interface { - SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) -} - // SendRegisterFact adds a fact for the user to user discovery. Will only // succeed if the user is already registered and the system does not have the // fact currently registered for any user. @@ -24,16 +19,21 @@ type addFactComms interface { // 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 (m *Manager) SendRegisterFact(fact fact.Fact) (string, error) { - jww.INFO.Printf("ud.SendRegisterFact(%s)", fact.Stringify()) - return m.addFact(fact, m.myID, m.comms) +func (m *Manager) SendRegisterFact(f fact.Fact) (string, error) { + jww.INFO.Printf("ud.SendRegisterFact(%s)", f.Stringify()) + m.factMux.Lock() + defer m.factMux.Unlock() + return m.addFact(f, m.e2e.GetReceptionID(), m.comms) } -func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (string, error) { +// addFact is the helper function for SendRegisterFact. +func (m *Manager) addFact(inFact fact.Fact, myId *id.ID, + aFC addFactComms) (string, error) { - if !m.IsRegistered() { - return "", errors.New("Failed to add fact: " + - "client is not registered") + // get UD host + udHost, err := m.getOrAddUdHost() + if err != nil { + return "", err } // Create a primitives Fact so we can hash it @@ -46,38 +46,34 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin fHash := factID.Fingerprint(f) // Sign our inFact for putting into the request - fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) + privKey := m.user.PortableUserInfo().ReceptionRSA + fSig, err := rsa.Sign(rand.Reader, privKey, hash.CMixHash, fHash, nil) if err != nil { return "", err } // Create our Fact Removal Request message data remFactMsg := pb.FactRegisterRequest{ - UID: uid.Marshal(), + UID: myId.Marshal(), Fact: &pb.Fact{ - Fact: inFact.Fact, - FactType: uint32(inFact.T), + Fact: f.Fact, + FactType: uint32(f.T), }, FactSig: fSig, } - // get UD host - host, err := m.getHost() - if err != nil { - return "", err - } - // Send the message - response, err := aFC.SendRegisterFact(host, &remFactMsg) + response, err := aFC.SendRegisterFact(udHost, &remFactMsg) confirmationID := "" if response != nil { confirmationID = response.ConfirmationID } - err = m.storage.GetUd().StoreUnconfirmedFact(confirmationID, f) + err = m.store.StoreUnconfirmedFact(confirmationID, f) if err != nil { - return "", errors.WithMessagef(err, "Failed to store unconfirmed fact %v", f.Fact) + return "", errors.WithMessagef(err, + "Failed to store unconfirmed fact %v", f.Fact) } // Return the error return confirmationID, err diff --git a/ud/addFact_test.go b/ud/addFact_test.go index 7fecfc4d6d59a3a15666e662cbb149af52c6795d..1a1a38cdcd75f6b416786f76742bfe92ca990d64 100644 --- a/ud/addFact_test.go +++ b/ud/addFact_test.go @@ -2,13 +2,9 @@ package ud import ( jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" "os" "testing" @@ -31,28 +27,8 @@ func (rFC *testAFC) SendRegisterFact(*connect.Host, *pb.FactRegisterRequest) ( // Test that the addFact function completes successfully func TestAddFact(t *testing.T) { - isReg := uint32(1) - // Create a new Private Key to use for signing the Fact - rng := csprng.NewSystemRNG() - cpk, err := rsa.GenerateKey(rng, 2048) - if err != nil { - t.Fatal(err) - } - - comms, err := client.NewClientComms(nil, nil, nil, nil) - if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Create our Manager object - m := Manager{ - comms: comms, - net: newTestNetworkManager(t), - privKey: cpk, - registered: &isReg, - storage: storage.InitTestingSession(t), - } + m, _ := newTestManager(t) // Create our test fact USCountryCode := "US" @@ -68,7 +44,7 @@ func TestAddFact(t *testing.T) { tAFC := testAFC{} uid := &id.ID{} // Run addFact and see if it returns without an error! - _, err = m.addFact(f, uid, &tAFC) + _, err := m.addFact(f, uid, &tAFC) if err != nil { t.Fatal(err) } diff --git a/ud/alternate.go b/ud/alternate.go new file mode 100644 index 0000000000000000000000000000000000000000..33720102924ce123b7cb423093e994f25a9ec99f --- /dev/null +++ b/ud/alternate.go @@ -0,0 +1,63 @@ +package ud + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/primitives/id" +) + +// alternateUd is an alternative user discovery service. +// This is used for testing, so client can avoid contacting +// the production server. This requires an alternative, +// deployed UD service. +type alternateUd struct { + host *connect.Host + dhPubKey []byte +} + +// SetAlternativeUserDiscovery sets the alternativeUd object within manager. +// Once set, any user discovery operation will go through the alternative +// user discovery service. +// To undo this operation, use UnsetAlternativeUserDiscovery. +func (m *Manager) SetAlternativeUserDiscovery(altCert, altAddress, + contactFile []byte) error { + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + + udIdBytes, dhPubKey, err := contact.ReadContactFromFile(contactFile) + if err != nil { + return err + } + + udID, err := id.Unmarshal(udIdBytes) + if err != nil { + return err + } + + // Add a new host and return it if it does not already exist + host, err := m.comms.AddHost(udID, string(altAddress), + altCert, params) + if err != nil { + return errors.WithMessage(err, "User Discovery host object could "+ + "not be constructed.") + } + + m.alternativeUd = &alternateUd{ + host: host, + dhPubKey: dhPubKey, + } + + return nil +} + +// UnsetAlternativeUserDiscovery clears out the information from +// the Manager object. +func (m *Manager) UnsetAlternativeUserDiscovery() error { + if m.alternativeUd == nil { + return errors.New("Alternative User Discovery is already unset.") + } + + m.alternativeUd = nil + return nil +} diff --git a/ud/comms.go b/ud/comms.go new file mode 100644 index 0000000000000000000000000000000000000000..07a450a405a98750fb6a2bd9dd3424c5c6f5e478 --- /dev/null +++ b/ud/comms.go @@ -0,0 +1,70 @@ +package ud + +import ( + pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/comms/messages" + "gitlab.com/xx_network/primitives/id" +) + +// Comms is a sub-interface of the client.Comms interface. This contains +// RPCs and methods relevant to sending to the UD service. +type Comms interface { + // 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) + // 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) + // 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) + // 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) + // 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) + // 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) + // 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) +} + +// 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 +// permanentDeleteAccount 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/confirmFact.go b/ud/confirmFact.go index 7432911b2458e5744afacc77640b78eb2637153d..fb81b7a9ee78e373fb54aaa6504d22c4eb1fa35b 100644 --- a/ud/confirmFact.go +++ b/ud/confirmFact.go @@ -4,33 +4,23 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" pb "gitlab.com/elixxir/comms/mixmessages" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/comms/messages" ) -type confirmFactComm interface { - SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) -} - -// SendConfirmFact confirms a fact first registered via AddFact. The +// ConfirmFact confirms a fact first registered via AddFact. The // confirmation ID comes from AddFact while the code will come over the // associated communications system. -func (m *Manager) SendConfirmFact(confirmationID, code string) error { - jww.INFO.Printf("ud.SendConfirmFact(%s, %s)", confirmationID, code) +func (m *Manager) ConfirmFact(confirmationID, code string) error { + jww.INFO.Printf("ud.ConfirmFact(%s, %s)", confirmationID, code) if err := m.confirmFact(confirmationID, code, m.comms); err != nil { return errors.WithMessage(err, "Failed to confirm fact") } return nil } +// confirmFact is a helper function for ConfirmFact. func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm) error { - if !m.IsRegistered() { - return errors.New("Failed to confirm fact: " + - "client is not registered") - } - // get UD host - host, err := m.getHost() + udHost, err := m.getOrAddUdHost() if err != nil { return err } @@ -39,14 +29,16 @@ func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm) ConfirmationID: confirmationID, Code: code, } - _, err = comm.SendConfirmFact(host, msg) + _, err = comm.SendConfirmFact(udHost, msg) if err != nil { return err } - err = m.storage.GetUd().ConfirmFact(confirmationID) + err = m.store.ConfirmFact(confirmationID) if err != nil { - return errors.WithMessagef(err, "Failed to confirm fact in storage with confirmation ID: %q", confirmationID) + return errors.WithMessagef(err, + "Failed to confirm fact in storage with confirmation ID: %q", + confirmationID) } return nil diff --git a/ud/confirmFact_test.go b/ud/confirmFact_test.go index 4b9789b40d07d4ea3689d2caa5c90752ca1fea46..b59a3c385a499ff9685c5520056ac3c59cb0c9b0 100644 --- a/ud/confirmFact_test.go +++ b/ud/confirmFact_test.go @@ -1,8 +1,6 @@ package ud import ( - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/comms/connect" @@ -22,20 +20,9 @@ func (t *testComm) SendConfirmFact(_ *connect.Host, message *pb.FactConfirmReque // Happy path. func TestManager_confirmFact(t *testing.T) { - isReg := uint32(1) - comms, err := client.NewClientComms(nil, nil, nil, nil) - if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Set up manager - m := &Manager{ - comms: comms, - net: newTestNetworkManager(t), - registered: &isReg, - storage: storage.InitTestingSession(t), - } + // Create our Manager object + m, _ := newTestManager(t) c := &testComm{} @@ -45,7 +32,7 @@ func TestManager_confirmFact(t *testing.T) { } // Set up store for expected state - err = m.storage.GetUd().StoreUnconfirmedFact(expectedRequest.ConfirmationID, fact.Fact{}) + err := m.store.StoreUnconfirmedFact(expectedRequest.ConfirmationID, fact.Fact{}) if err != nil { t.Fatalf("StoreUnconfirmedFact error: %v", err) } diff --git a/ud/interfaces.go b/ud/interfaces.go new file mode 100644 index 0000000000000000000000000000000000000000..33db0140789e9185b671f7dceee8f05dcc177098 --- /dev/null +++ b/ud/interfaces.go @@ -0,0 +1,38 @@ +package ud + +import ( + "gitlab.com/elixxir/client/api" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/client/storage/user" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/primitives/id" +) + +// CMix is a sub-interface of the cmix.Client. It contains the methods +// relevant to what is used in this package. +type CMix interface { + // CMix is passed down into the single use package, + // and thus has to adhere to the sub-interface defined in that package + single.CMix +} + +// E2E is a sub-interface of the e2e.Handler. It contains the methods +// relevant to what is used in this package. +type E2E interface { + // GetGroup returns the cyclic group used for end to end encruption + GetGroup() *cyclic.Group + + // GetReceptionID returns the default IDs + GetReceptionID() *id.ID +} + +// UserInfo is a sub-interface for the user.User object in storage. +// It contains the methods relevant to what is used in this package. +type UserInfo interface { + PortableUserInfo() user.Info + GetReceptionRegistrationValidationSignature() []byte +} + +// NetworkStatus is an interface for the api.Client's +// NetworkFollowerStatus method. +type NetworkStatus func() api.Status diff --git a/ud/lookup.go b/ud/lookup.go index 2ba65223404fc234380a31b60774d5ce2cdbcd0c..bdcf557b8ebffd2bd21b75e7ed7ba20d58dedd54 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -4,39 +4,51 @@ import ( "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/primitives/fact" + "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" - "time" ) // LookupTag specifies which callback to trigger when UD receives a lookup // request. 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 (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error { - jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout) - return m.lookup(uid, callback, timeout) +func Lookup(services CMix, + rng csprng.Source, grp *cyclic.Group, + udContact contact.Contact, callback lookupCallback, + uid *id.ID, p single.RequestParams) ([]id.Round, + receptionID.EphemeralIdentity, error) { + + jww.INFO.Printf("ud.Lookup(%s, %s)", uid, p.Timeout) + return lookup(services, rng, uid, grp, udContact, callback, p) } // 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 (m *Manager) BatchLookup(uids []*id.ID, callback lookupCallback, timeout time.Duration) { - jww.INFO.Printf("ud.BatchLookup(%s, %s)", uids, timeout) +func BatchLookup(udContact contact.Contact, + services CMix, callback lookupCallback, + rng csprng.Source, + uids []*id.ID, grp *cyclic.Group, + p single.RequestParams) { + jww.INFO.Printf("ud.BatchLookup(%s, %s)", uids, p.Timeout) for _, uid := range uids { go func(localUid *id.ID) { - err := m.lookup(localUid, callback, timeout) + _, _, err := lookup(services, rng, localUid, grp, + udContact, callback, p) 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) } @@ -47,67 +59,73 @@ func (m *Manager) BatchLookup(uids []*id.ID, callback lookupCallback, timeout ti // 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 (m *Manager) lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error { +func lookup(net CMix, rng csprng.Source, uid *id.ID, + grp *cyclic.Group, udContact contact.Contact, + callback lookupCallback, + p single.RequestParams) ( + []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.") - } - - f := func(payload []byte, err error) { - m.lookupResponseProcess(uid, callback, payload, err) - } - - // get UD contact - c, err := m.getContact() - if err != nil { - return err + return []id.Round{}, + receptionID.EphemeralIdentity{}, errors.WithMessage(err, + "Failed to form outgoing lookup request.") } - err = m.single.TransmitSingleUse(c, requestMarshaled, LookupTag, - maxLookupMessages, f, timeout) - if err != nil { - return errors.WithMessage(err, "Failed to transmit lookup request.") + response := lookupResponse{ + cb: callback, + uid: uid, + grp: grp, } - return nil + return single.TransmitRequest(udContact, LookupTag, requestMarshaled, + response, p, net, rng, + grp) } -// 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, callback lookupCallback, - payload []byte, err error) { +type lookupResponse struct { + cb lookupCallback + uid *id.ID + grp *cyclic.Group +} + +func (m lookupResponse) Callback(payload []byte, + receptionID receptionID.EphemeralIdentity, + rounds []rounds.Round, err error) { + if err != nil { - go 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: m.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) } diff --git a/ud/lookup_test.go b/ud/lookup_test.go index a6debf89424b24b44ad70033e32d947541277cf8..23311707d472990ef9028c075e37487ea1300e70 100644 --- a/ud/lookup_test.go +++ b/ud/lookup_test.go @@ -1,213 +1,111 @@ package ud import ( - "github.com/golang/protobuf/proto" - "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/single" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" - "math/rand" "reflect" - "strings" + "strconv" "testing" "time" ) // Happy path. func TestManager_Lookup(t *testing.T) { - // Set up manager - isReg := uint32(1) - - comms, err := client.NewClientComms(nil, nil, nil, nil) + // Set up mock UD values + grp := getGroup() + prng := NewPrng(42) + privKeyBytes, err := csprng.GenerateInGroup( + grp.GetP().Bytes(), grp.GetP().ByteLen(), prng) if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - m := &Manager{ - comms: comms, - storage: storage.InitTestingSession(t), - net: newTestNetworkManager(t), - grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2)), - single: &mockSingleLookup{}, - registered: &isReg, + t.Fatalf("Failed to generate a mock private key: %v", err) } + udMockPrivKey := grp.NewIntFromBytes(privKeyBytes) + publicKey := grp.ExpG(udMockPrivKey, grp.NewInt(1)) // Generate callback function - callbackChan := make(chan struct { - c contact.Contact - err error - }) + callbackChan := make(mockChannel) callback := func(c contact.Contact, err error) { - callbackChan <- struct { - c contact.Contact - err error - }{c: c, err: err} - } - uid := id.NewIdFromUInt(0x500000000000000, id.User, t) - - // Run the lookup - err = m.Lookup(uid, callback, 10*time.Millisecond) - if err != nil { - t.Errorf("Lookup() returned an error: %+v", err) + callbackChan <- mockResponse{ + c: []contact.Contact{c}, + err: err, + } } - // Verify the callback is called - select { - case cb := <-callbackChan: - if cb.err != nil { - t.Errorf("Callback returned an error: %+v", cb.err) - } + // Set up mock manager + m, tnm := newTestManager(t) - expectedContact := contact.Contact{ - ID: uid, - DhPubKey: m.grp.NewIntFromBytes([]byte{5}), - } - if !reflect.DeepEqual(expectedContact, cb.c) { - t.Errorf("Failed to get expected Contact."+ - "\n\texpected: %v\n\treceived: %v", expectedContact, cb.c) - } - case <-time.After(100 * time.Millisecond): - t.Error("Callback not called.") + udContact, err := m.GetContact() + if err != nil { + t.Fatalf("Failed to get contact: %v", err) } -} -// Happy path. -func TestManager_lookupResponseProcess(t *testing.T) { - m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))} - - 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} - } - pubKey := []byte{5} + uid := id.NewIdFromUInt(0x500000000000000, id.User, t) expectedContact := contact.Contact{ ID: uid, - DhPubKey: m.grp.NewIntFromBytes(pubKey), - } - - // Generate expected Send message - payload, err := proto.Marshal(&LookupResponse{PubKey: pubKey}) - if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) + DhPubKey: publicKey, } - m.lookupResponseProcess(uid, callback, payload, nil) - - select { - case results := <-callbackChan: - if results.err != nil { - t.Errorf("Callback returned an error: %+v", results.err) - } - if !reflect.DeepEqual(expectedContact, results.c) { - t.Errorf("Callback returned unexpected Contact."+ - "\nexpected: %+v\nreceived: %+v", expectedContact, results.c) - } - case <-time.NewTimer(50 * time.Millisecond).C: - t.Error("Callback time out.") - } -} + contacts := []*Contact{&Contact{ + UserID: expectedContact.ID.Bytes(), + PubKey: expectedContact.DhPubKey.Bytes(), + }} -// 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))} + receiver := newMockReceiver(callbackChan, contacts, 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} + udbId, err := id.Unmarshal(tnm.instance.GetFullNdf().Get().UDB.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) } - testErr := errors.New("lookup failure") + mockListener := single.Listen(LookupTag, udbId, udMockPrivKey, + tnm, grp, receiver) - m.lookupResponseProcess(nil, callback, []byte{}, testErr) + defer mockListener.Stop() - 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.") + r := m.e2e.GetGroup().NewInt(1) + m.e2e.GetGroup().Random(r) + s := "" + jsonable, err := r.MarshalJSON() + if err != nil { + t.Fatalf("failed to marshal json: %v", err) + } + for _, b := range jsonable { + s += strconv.Itoa(int(b)) + ", " } -} -// 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))} + t.Logf("%v", r.Bytes()) + t.Logf("%s", s) - 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} + timeout := 500 * time.Millisecond + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: 1, + CmixParam: cmix.GetDefaultCMIXParams(), } - // Generate expected Send message - testErr := "LookupResponse error occurred" - payload, err := proto.Marshal(&LookupResponse{Error: testErr}) + // Run the lookup + _, _, err = Lookup(m.network, prng, + grp, udContact, callback, uid, p) if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) + t.Errorf("Lookup() returned an error: %+v", err) } - m.lookupResponseProcess(uid, callback, payload, nil) - + // Verify the callback is called 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 cb := <-callbackChan: + if cb.err != nil { + t.Errorf("Callback returned an error: %+v", cb.err) } - case <-time.NewTimer(50 * time.Millisecond).C: - t.Error("Callback time out.") - } -} - -// mockSingleLookup is used to test the lookup function, which uses the single- -// use manager. It adheres to the SingleInterface interface. -type mockSingleLookup struct { -} -func (s *mockSingleLookup) TransmitSingleUse(_ contact.Contact, payload []byte, - _ string, _ uint8, callback single.ReplyCallback, _ time.Duration) error { - - lookupMsg := &LookupSend{} - if err := proto.Unmarshal(payload, lookupMsg); err != nil { - return errors.Errorf("Failed to unmarshal LookupSend: %+v", err) - } - - lookupResponse := &LookupResponse{PubKey: lookupMsg.UserID[:1]} - msg, err := proto.Marshal(lookupResponse) - if err != nil { - return errors.Errorf("Failed to marshal LookupResponse: %+v", err) + if !reflect.DeepEqual([]contact.Contact{expectedContact}, + cb.c) { + t.Errorf("Failed to get expected Contact."+ + "\n\texpected: %v\n\treceived: %v", expectedContact, cb.c) + } + case <-time.After(100 * time.Millisecond): + t.Error("Callback not called.") } - - callback(msg, nil) - return nil -} - -func (s *mockSingleLookup) StartProcesses() (stoppable.Stoppable, error) { - return stoppable.NewSingle(""), nil } diff --git a/ud/manager.go b/ud/manager.go index 6bd6b7b1a7f5f78532509d00c8a5f89ed4437651..53e6a37f62630e24f0754ac4b731bddf01e37c0c 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -1,225 +1,230 @@ package ud import ( + "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" - "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/single" - "gitlab.com/elixxir/client/single/old" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" + "gitlab.com/elixxir/client/event" + "gitlab.com/elixxir/client/storage/versioned" + store "gitlab.com/elixxir/client/ud/store" "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/cyclic" - "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/crypto/csprng" "gitlab.com/xx_network/primitives/id" - "math" + "sync" "time" ) -type SingleInterface interface { - TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyCallback, - time.Duration) error - StartProcesses() (stoppable.Stoppable, error) -} - +// Manager is the control structure for the contacting the user discovery service. type Manager struct { - // External - client *api.Client - comms *client.Comms - rng *fastRNG.StreamGenerator - sw interfaces.Switchboard - storage *storage.Session - net interfaces.NetworkManager - - // Loaded from external access - privKey *rsa.PrivateKey - grp *cyclic.Group - - // internal structures - single SingleInterface - myID *id.ID - - // alternate User discovery service to circumvent production + // Network is a sub-interface of the cmix.Client interface. It + // allows the Manager to retrieve network state. + network CMix + + // e2e is a sub-interface of the e2e.Handler. It allows the Manager + // to retrieve the client's E2E information. + e2e E2E + + // events allows the Manager to report events to the other + // levels of the client. + events event.Reporter + + // store is an instantiation of this package's storage object. + // It contains the facts that are in some state of being registered + // with the UD service + store *store.Store + + // user is a sub-interface of the user.User object in the storage package. + // This allows the Manager to pull user information for registration + // and verifying the client's identity + user UserInfo + + // comms is a sub-interface of the client.Comms interface. It contains + // gRPC functions for registering and fact operations. + comms Comms + + // kv is a versioned key-value store used for isRegistered and + // setRegistered. This is separated from store operations as store's kv + // has a different prefix which breaks backwards compatibility. + kv *versioned.KV + + // factMux is to be used for Add/Remove fact.Fact operations. + // This prevents simultaneous calls to Add/Remove calls which + // may cause unexpected behaviour. + factMux sync.Mutex + + // alternativeUd is an alternate User discovery service to circumvent + // production. This is for testing with a separately deployed UD service. alternativeUd *alternateUd - - registered *uint32 } -// alternateUd is an alternative user discovery service. -// This is used for testing, so client can avoid using -// the production server. -type alternateUd struct { - host *connect.Host - dhPubKey []byte -} - -// NewManager builds a new user discovery manager. It requires that an updated +// NewManager builds a new user discovery manager. +// It requires that an updated // NDF is available and will error if one is not. -func NewManager(client *api.Client, single *old.Manager) (*Manager, error) { +func NewManager(services CMix, e2e E2E, + follower NetworkStatus, + events event.Reporter, comms Comms, userStore UserInfo, + rng csprng.Source, username string, + kv *versioned.KV) (*Manager, error) { jww.INFO.Println("ud.NewManager()") - if client.NetworkFollowerStatus() != api.Running { + + if follower() != api.Running { return nil, errors.New( "cannot start UD Manager when network follower is not running.") } + // Initialize manager m := &Manager{ - client: client, - comms: client.GetComms(), - rng: client.GetRng(), - sw: client.GetSwitchboard(), - storage: client.GetStorage(), - net: client.GetNetworkInterface(), - single: single, + network: services, + e2e: e2e, + events: events, + comms: comms, + user: userStore, + kv: kv, } - // check that user discovery is available in the NDF - def := m.net.GetInstance().GetPartialNdf().Get() - - if def.UDB.Cert == "" { - return nil, errors.New("NDF does not have User Discovery information, " + - "is there network access?: Cert not present.") + // Initialize store + var err error + m.store, err = store.NewOrLoadStore(kv) + if err != nil { + return nil, errors.Errorf("Failed to initialize store: %v", err) } - // Create the user discovery host object - hp := connect.GetDefaultHostParams() - // Client will not send KeepAlive packets - hp.KaClientOpts.Time = time.Duration(math.MaxInt64) - hp.MaxRetries = 3 - hp.SendTimeout = 3 * time.Second - hp.AuthEnabled = false - - m.myID = m.storage.User().GetCryptographicIdentity().GetReceptionID() - - // get the commonly used data from storage - m.privKey = m.storage.GetUser().ReceptionRSA + // Initialize/Get host + udHost, err := m.getOrAddUdHost() + if err != nil { + return nil, errors.WithMessage(err, "User Discovery host object could "+ + "not be constructed.") + } - // Load if the client is registered - m.loadRegistered() + // Register with user discovery + err = m.register(username, rng, comms, udHost) + if err != nil { + return nil, errors.Errorf("Failed to register: %v", err) + } - // Store the pointer to the group locally for easy access - m.grp = m.storage.E2e().GetGroup() + // Set storage to registered + if err = m.setRegistered(); err != nil && m.events != nil { + m.events.Report(1, "UserDiscovery", "Registration", + fmt.Sprintf("User Registered with UD: %+v", + username)) + } return m, nil } -// SetAlternativeUserDiscovery sets the alternativeUd object within manager. -// Once set, any user discovery operation will go through the alternative -// user discovery service. -// To undo this operation, use UnsetAlternativeUserDiscovery. -func (m *Manager) SetAlternativeUserDiscovery(altCert, altAddress, contactFile []byte) error { - params := connect.GetDefaultHostParams() - params.AuthEnabled = false +// 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, + e2e E2E, follower NetworkStatus, + events event.Reporter, comms Comms, userStore UserInfo, + email, phone fact.Fact, kv *versioned.KV) (*Manager, error) { + jww.INFO.Println("ud.NewManagerFromBackup()") + if follower() != api.Running { + return nil, errors.New( + "cannot start UD Manager when " + + "network follower is not running.") + } + + // Initialize manager + m := &Manager{ + network: services, + e2e: e2e, + events: events, + comms: comms, + user: userStore, + kv: kv, + } - udIdBytes, dhPubKey, err := contact.ReadContactFromFile(contactFile) + // Initialize our store + var err error + m.store, err = store.NewOrLoadStore(kv) if err != nil { - return err + return nil, err } - udID, err := id.Unmarshal(udIdBytes) + // Put any passed in missing facts into store + err = m.store.BackUpMissingFacts(email, phone) if err != nil { - return err + return nil, errors.WithMessage(err, "Failed to restore UD store "+ + "from backup") } - // Add a new host and return it if it does not already exist - host, err := m.comms.AddHost(udID, string(altAddress), - altCert, params) + // Create the user discovery host object + _, err = m.getOrAddUdHost() if err != nil { - return errors.WithMessage(err, "User Discovery host object could "+ + return nil, errors.WithMessage(err, "User Discovery host object could "+ "not be constructed.") } - m.alternativeUd = &alternateUd{ - host: host, - dhPubKey: dhPubKey, + // Set as registered. Since it's from a backup, + // the client is already registered + if err = m.setRegistered(); err != nil { + return nil, errors.WithMessage(err, "failed to set client as "+ + "registered with user discovery.") } - return nil + return m, nil } -// UnsetAlternativeUserDiscovery clears out the information from -// the Manager object. -func (m *Manager) UnsetAlternativeUserDiscovery() error { - if m.alternativeUd == nil { - return errors.New("Alternative User Discovery is already unset.") +// LoadManager loads the state of the Manager +// from disk. This is meant to be called after any the first +// instantiation of the manager by NewUserDiscovery. +func LoadManager(services CMix, e2e E2E, + events event.Reporter, comms Comms, userStore UserInfo, + kv *versioned.KV) (*Manager, error) { + + m := &Manager{ + network: services, + e2e: e2e, + events: events, + comms: comms, + user: userStore, + + kv: kv, } - m.alternativeUd = nil - return nil -} + if !m.isRegistered() { + return nil, errors.Errorf("LoadManager could not detect that " + + "the user has been registered. Has a manager been initiated before?") + } + + var err error + m.store, err = store.NewOrLoadStore(kv) + if err != nil { + return nil, errors.Errorf("Failed to initialize store: %v", err) + } -// 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 nil, however both is considered -// an error. It checks for the proper fact type for the associated fact. -// 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 (m *Manager) BackUpMissingFacts(email, phone fact.Fact) error { - return m.storage.GetUd().BackUpMissingFacts(email, phone) + return m, err } // GetFacts returns a list of fact.Fact objects that exist within the // Store's registeredFacts map. func (m *Manager) GetFacts() []fact.Fact { - return m.storage.GetUd().GetFacts() + return m.store.GetFacts() } // GetStringifiedFacts returns a list of stringified facts from the Store's // registeredFacts map. func (m *Manager) GetStringifiedFacts() []string { - return m.storage.GetUd().GetStringifiedFacts() + return m.store.GetStringifiedFacts() } -// getHost returns the current UD host for the UD ID found in the NDF. If the -// host does not exist, then it is added and returned -func (m *Manager) getHost() (*connect.Host, error) { - // Return alternative User discovery service if it has been set - if m.alternativeUd != nil { - return m.alternativeUd.host, nil - } - - netDef := m.net.GetInstance().GetPartialNdf().Get() - // Unmarshal UD ID from the NDF - udID, err := id.Unmarshal(netDef.UDB.ID) - if err != nil { - return nil, errors.Errorf("failed to unmarshal UD ID from NDF: %+v", err) - } - - // Return the host, if it exists - host, exists := m.comms.GetHost(udID) - if exists { - return host, nil - } - - params := connect.GetDefaultHostParams() - params.AuthEnabled = false - params.SendTimeout = 20 * time.Second - - // Add a new host and return it if it does not already exist - host, err = m.comms.AddHost(udID, netDef.UDB.Address, - []byte(netDef.UDB.Cert), params) - if err != nil { - return nil, errors.WithMessage(err, "User Discovery host object could "+ - "not be constructed.") - } - - return host, nil -} - -// getContact returns the contact for UD as retrieved from the NDF. -func (m *Manager) getContact() (contact.Contact, error) { +// GetContact returns the contact for UD as retrieved from the NDF. +func (m *Manager) GetContact() (contact.Contact, error) { + grp := m.e2e.GetGroup() // Return alternative User discovery contact if set if m.alternativeUd != nil { // Unmarshal UD DH public key - alternativeDhPubKey := m.storage.E2e().GetGroup().NewInt(1) - if err := alternativeDhPubKey.UnmarshalJSON(m.alternativeUd.dhPubKey); err != nil { + alternativeDhPubKey := grp.NewInt(1) + if err := alternativeDhPubKey. + UnmarshalJSON(m.alternativeUd.dhPubKey); err != nil { return contact.Contact{}, - errors.WithMessage(err, "Failed to unmarshal UD DH public key.") + errors.WithMessage(err, "Failed to unmarshal UD "+ + "DH public key.") } return contact.Contact{ @@ -230,7 +235,7 @@ func (m *Manager) getContact() (contact.Contact, error) { }, nil } - netDef := m.net.GetInstance().GetPartialNdf().Get() + netDef := m.network.GetInstance().GetPartialNdf().Get() // Unmarshal UD ID from the NDF udID, err := id.Unmarshal(netDef.UDB.ID) @@ -240,10 +245,11 @@ func (m *Manager) getContact() (contact.Contact, error) { } // Unmarshal UD DH public key - dhPubKey := m.storage.E2e().GetGroup().NewInt(1) + dhPubKey := grp.NewInt(1) if err = dhPubKey.UnmarshalJSON(netDef.UDB.DhPubKey); err != nil { return contact.Contact{}, - errors.WithMessage(err, "Failed to unmarshal UD DH public key.") + errors.WithMessage(err, "Failed to unmarshal UD DH "+ + "public key.") } return contact.Contact{ @@ -253,3 +259,45 @@ func (m *Manager) getContact() (contact.Contact, error) { Facts: nil, }, nil } + +// getOrAddUdHost returns the current UD host for the UD ID found in the NDF. +// If the host does not exist, then it is added and returned. +func (m *Manager) getOrAddUdHost() (*connect.Host, error) { + // Return alternative User discovery service if it has been set + if m.alternativeUd != nil { + return m.alternativeUd.host, nil + } + + netDef := m.network.GetInstance().GetPartialNdf().Get() + if netDef.UDB.Cert == "" { + return nil, errors.New("NDF does not have User Discovery information, " + + "is there network access?: Cert not present.") + } + + // Unmarshal UD ID from the NDF + udID, err := id.Unmarshal(netDef.UDB.ID) + if err != nil { + return nil, errors.Errorf("failed to "+ + "unmarshal UD ID from NDF: %+v", err) + } + + // Return the host, if it exists + host, exists := m.comms.GetHost(udID) + if exists { + return host, nil + } + + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + params.SendTimeout = 20 * time.Second + + // Add a new host and return it if it does not already exist + host, err = m.comms.AddHost(udID, netDef.UDB.Address, + []byte(netDef.UDB.Cert), params) + if err != nil { + return nil, errors.WithMessage(err, "User Discovery host "+ + "object could not be constructed.") + } + + return host, nil +} diff --git a/ud/manager_test.go b/ud/manager_test.go index 1dcb6c273df1c6ec7ed92980b15fc8990cfab01a..1bf18a58b4f3aa41915eddecc789c2c1f240fb4f 100644 --- a/ud/manager_test.go +++ b/ud/manager_test.go @@ -8,9 +8,6 @@ package ud import ( - "gitlab.com/elixxir/comms/client" - "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/crypto/signature/rsa" "testing" ) @@ -53,30 +50,10 @@ EnretBzQkeKeBwoB2u6NTiOmUjk= var testContact = `<xxc(2)LF2ccT+sdqh0AIKlFFeDOJdnxzbQQYhGStgxhOXmijIDkAZiB9kZo+Dl3bRSbBi5pXZ82rOu2IQXz9+5sspChvoccZqgC/dXGhlesmiNy/EbKxWtptTF4tcNyQxtnmCXg1p/HwKey4G2XDekTw86lq6Lpmj72jozvRWlQisqvWz/5deiPaeFGKDKC0OrrDFnIib7WnKqdYt4XyTKdmObnmbvdCbliZq0zBl7J40qKy5FypYXGlZjStIm0R1qtD4XHMZMsrMJEGxdM55zJdSzknXbR8MNahUrGMyUOTivXLHzojYLht0gFQifKMVWhrDjUoVQV43KOLPmdBwY/2Kc5KvVloDeuDXYY0i7tD63gNIp9JA3gJQUJymDdwqbS13riT1DMHHkdTzKEyGdHS+v2l7AVSlJBiTKuyM00FBNuXhhIcFR7ONFCf8cRPOPPBx3Q6iHNsvsca3KPNhwOJBgaQvHSkjIMsudiR954QbwG9rbi2vxVobIgWYMl5j6vlBS/9rfbE/uLdTEQZfNsLKDCIVCCI4I1bYZxZrDLPrfXTrN6W0sCLE7a/kRBQAAAgA7+LwJqiv9O1ogLnS4TYkSEg==xxc>` func TestManager_SetAlternativeUserDiscovery(t *testing.T) { - isReg := uint32(1) - - // Create a new Private Key to use for signing the Fact - rng := csprng.NewSystemRNG() - cpk, err := rsa.GenerateKey(rng, 2048) - if err != nil { - t.Fatal(err) - } - - comms, err := client.NewClientComms(nil, nil, nil, nil) - if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Create our Manager object - m := Manager{ - comms: comms, - net: newTestNetworkManager(t), - privKey: cpk, - registered: &isReg, - } + m, _ := newTestManager(t) altAddr := "0.0.0.0:11420" - err = m.SetAlternativeUserDiscovery([]byte(testCert), []byte(altAddr), []byte(testContact)) + err := m.SetAlternativeUserDiscovery([]byte(testCert), []byte(altAddr), []byte(testContact)) if err != nil { t.Fatalf("Unexpected error in SetAlternativeUserDiscovery: %v", err) } diff --git a/ud/register.go b/ud/register.go index 55e62c1f804dd7f18de75b0135f8b789f9992a53..caae813dc950dbe260a07b19ce5be12b6a5db2e7 100644 --- a/ud/register.go +++ b/ud/register.go @@ -1,61 +1,40 @@ package ud import ( - "fmt" "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/factID" "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/csprng" "gitlab.com/xx_network/crypto/signature/rsa" ) -type registerUserComms interface { - SendRegisterUser(*connect.Host, *pb.UDBUserRegistration) (*messages.Ack, error) -} - -// Register registers a user with user discovery. Will return an error if the -// network signatures are malformed or if the username is taken. Usernames 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 (m *Manager) Register(username string) error { - jww.INFO.Printf("ud.Register(%s)", username) - return m.register(username, m.comms) -} - -// register registers a user with user discovery with a specified comm for -// easier testing. -func (m *Manager) register(username string, comm registerUserComms) error { - if m.IsRegistered() { - return errors.New("cannot register client with User Discovery: " + - "client is already registered") - } +// register initiates registration with user discovery given a specified +// username. Provided a comms sub-interface to facilitate testing. +func (m *Manager) register(username string, rng csprng.Source, + comm registerUserComms, udHost *connect.Host) error { var err error - user := m.storage.User() - cryptoUser := m.storage.User().GetCryptographicIdentity() - rng := m.rng.GetStream() + cryptoUser := m.user.PortableUserInfo() // Construct the user registration message msg := &pb.UDBUserRegistration{ - PermissioningSignature: user.GetReceptionRegistrationValidationSignature(), - RSAPublicPem: string(rsa.CreatePublicKeyPem(cryptoUser.GetReceptionRSA().GetPublic())), + PermissioningSignature: m.user.GetReceptionRegistrationValidationSignature(), + RSAPublicPem: string(rsa.CreatePublicKeyPem(cryptoUser.ReceptionRSA.GetPublic())), IdentityRegistration: &pb.Identity{ Username: username, - DhPubKey: m.storage.E2e().GetDHPublicKey().Bytes(), - Salt: cryptoUser.GetReceptionSalt(), + DhPubKey: cryptoUser.E2eDhPublicKey.Bytes(), + Salt: cryptoUser.ReceptionSalt, }, - UID: cryptoUser.GetReceptionID().Marshal(), - Timestamp: user.GetRegistrationTimestamp().UnixNano(), + UID: cryptoUser.ReceptionID.Marshal(), + Timestamp: cryptoUser.RegistrationTimestamp, } // Sign the identity data and add to user registration message identityDigest := msg.IdentityRegistration.Digest() - msg.IdentitySignature, err = rsa.Sign(rng, cryptoUser.GetReceptionRSA(), + msg.IdentitySignature, err = rsa.Sign(rng, cryptoUser.ReceptionRSA, hash.CMixHash, identityDigest, nil) if err != nil { return errors.Errorf("Failed to sign user's IdentityRegistration: %+v", err) @@ -69,11 +48,11 @@ func (m *Manager) register(username string, comm registerUserComms) error { // Hash and sign fact hashedFact := factID.Fingerprint(usernameFact) - signedFact, err := rsa.Sign(rng, cryptoUser.GetReceptionRSA(), hash.CMixHash, hashedFact, nil) + signedFact, err := rsa.Sign(rng, cryptoUser.ReceptionRSA, hash.CMixHash, hashedFact, nil) // Add username fact register request to the user registration message msg.Frs = &pb.FactRegisterRequest{ - UID: cryptoUser.GetReceptionID().Marshal(), + UID: cryptoUser.ReceptionID.Marshal(), Fact: &pb.Fact{ Fact: username, FactType: 0, @@ -81,23 +60,7 @@ func (m *Manager) register(username string, comm registerUserComms) error { FactSig: signedFact, } - // get UD host - host, err := m.getHost() - if err != nil { - return err - } - // Register user with user discovery - _, err = comm.SendRegisterUser(host, msg) - - if err == nil { - err = m.setRegistered() - if m.client != nil { - m.client.ReportEvent(1, "UserDiscovery", "Registration", - fmt.Sprintf("User Registered with UD: %+v", - user)) - } - } - + _, err = comm.SendRegisterUser(udHost, msg) return err } diff --git a/ud/register_test.go b/ud/register_test.go index 0bdd3ef54f664235549b98e40f380f7bc21585c3..3aae46e19b7a3834e3f16c6f42a35e8e696e20bc 100644 --- a/ud/register_test.go +++ b/ud/register_test.go @@ -2,16 +2,12 @@ package ud import ( "bytes" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/crypto/factID" - "gitlab.com/elixxir/crypto/fastRNG" "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/csprng" "gitlab.com/xx_network/crypto/signature/rsa" "reflect" "testing" @@ -28,34 +24,26 @@ func (t *testRegisterComm) SendRegisterUser(_ *connect.Host, msg *pb.UDBUserRegi // Happy path. func TestManager_register(t *testing.T) { - isReg := uint32(0) + m, _ := newTestManager(t) - comms, err := client.NewClientComms(nil, nil, nil, nil) + udHost, err := m.getOrAddUdHost() if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Set up manager - m := &Manager{ - comms: comms, - net: newTestNetworkManager(t), - rng: fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG), - storage: storage.InitTestingSession(t), - registered: &isReg, + t.Fatalf("Failed to get/add ud host: %+v", err) } c := &testRegisterComm{} + prng := NewPrng(42) - err = m.register("testUser", c) + err = m.register("testUser", prng, c, udHost) if err != nil { t.Errorf("register() returned an error: %+v", err) } // Check if the UDBUserRegistration contents are correct - m.isCorrect("testUser", c.msg, t) + isCorrect("testUser", c.msg, m, t) // Verify the signed identity data - pubKey := m.storage.User().GetCryptographicIdentity().GetTransmissionRSA().GetPublic() + pubKey := m.user.PortableUserInfo().ReceptionRSA.GetPublic() err = rsa.Verify(pubKey, hash.CMixHash, c.msg.IdentityRegistration.Digest(), c.msg.IdentitySignature, nil) if err != nil { @@ -73,18 +61,19 @@ func TestManager_register(t *testing.T) { // isCorrect checks if the UDBUserRegistration has all the expected fields minus // any signatures. -func (m *Manager) isCorrect(username string, msg *pb.UDBUserRegistration, t *testing.T) { - user := m.storage.User() - cryptoUser := m.storage.User().GetCryptographicIdentity() +func isCorrect(username string, msg *pb.UDBUserRegistration, m *Manager, t *testing.T) { + userInfo := m.user.PortableUserInfo() - if !bytes.Equal(user.GetTransmissionRegistrationValidationSignature(), msg.PermissioningSignature) { + if !bytes.Equal(m.user.GetReceptionRegistrationValidationSignature(), msg.PermissioningSignature) { t.Errorf("PermissioningSignature incorrect.\n\texpected: %v\n\treceived: %v", - user.GetTransmissionRegistrationValidationSignature(), msg.PermissioningSignature) + m.user.GetReceptionRegistrationValidationSignature(), msg.PermissioningSignature) } - if string(rsa.CreatePublicKeyPem(cryptoUser.GetTransmissionRSA().GetPublic())) != msg.RSAPublicPem { + if string(rsa.CreatePublicKeyPem(userInfo.TransmissionRSA.GetPublic())) != + msg.RSAPublicPem { t.Errorf("RSAPublicPem incorrect.\n\texpected: %v\n\treceived: %v", - string(rsa.CreatePublicKeyPem(cryptoUser.GetTransmissionRSA().GetPublic())), msg.RSAPublicPem) + string(rsa.CreatePublicKeyPem(userInfo.TransmissionRSA.GetPublic())), + msg.RSAPublicPem) } if username != msg.IdentityRegistration.Username { @@ -92,19 +81,19 @@ func (m *Manager) isCorrect(username string, msg *pb.UDBUserRegistration, t *tes username, msg.IdentityRegistration.Username) } - if !bytes.Equal(m.storage.E2e().GetDHPublicKey().Bytes(), msg.IdentityRegistration.DhPubKey) { + if !bytes.Equal(userInfo.E2eDhPublicKey.Bytes(), msg.IdentityRegistration.DhPubKey) { t.Errorf("IdentityRegistration DhPubKey incorrect.\n\texpected: %#v\n\treceived: %#v", - m.storage.E2e().GetDHPublicKey().Bytes(), msg.IdentityRegistration.DhPubKey) + userInfo.E2eDhPublicKey.Bytes(), msg.IdentityRegistration.DhPubKey) } - if !bytes.Equal(cryptoUser.GetTransmissionSalt(), msg.IdentityRegistration.Salt) { + if !bytes.Equal(userInfo.TransmissionSalt, msg.IdentityRegistration.Salt) { t.Errorf("IdentityRegistration Salt incorrect.\n\texpected: %#v\n\treceived: %#v", - cryptoUser.GetTransmissionSalt(), msg.IdentityRegistration.Salt) + userInfo.TransmissionSalt, msg.IdentityRegistration.Salt) } - if !bytes.Equal(cryptoUser.GetTransmissionID().Marshal(), msg.Frs.UID) { + if !bytes.Equal(userInfo.TransmissionID.Marshal(), msg.Frs.UID) { t.Errorf("Frs UID incorrect.\n\texpected: %v\n\treceived: %v", - cryptoUser.GetTransmissionID().Marshal(), msg.Frs.UID) + userInfo.TransmissionID.Marshal(), msg.Frs.UID) } if !reflect.DeepEqual(&pb.Fact{Fact: username}, msg.Frs.Fact) { diff --git a/ud/registered.go b/ud/registered.go index d81e615894e6928f5e7a529cf10c2ca288f21e04..47104cc2f56315fcbe11dbf925f7d415f2dbf132 100644 --- a/ud/registered.go +++ b/ud/registered.go @@ -2,53 +2,36 @@ package ud import ( "encoding/binary" - "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/xx_network/primitives/netTime" - "sync/atomic" ) const isRegisteredKey = "isRegisteredKey" const isRegisteredVersion = 0 -// loadRegistered loads from storage if the client is registered with user +// isRegistered loads from storage if the client is registered with user // discovery. -func (m *Manager) loadRegistered() { - var isReg = uint32(0) - obj, err := m.storage.Get(isRegisteredKey) +func (m *Manager) isRegistered() bool { + _, err := m.kv.Get(isRegisteredKey, isRegisteredVersion) if err != nil { - jww.INFO.Printf("Failed to load is registered, "+ - "assuming un-registered: %s", err) - } else { - isReg = binary.BigEndian.Uint32(obj.Data) + return false } - m.registered = &isReg + return true } -// IsRegistered returns if the client is registered with user discovery -func (m *Manager) IsRegistered() bool { - return atomic.LoadUint32(m.registered) == 1 -} - -// IsRegistered returns if the client is registered with user discovery +// isRegistered returns if the client is registered with user discovery func (m *Manager) setRegistered() error { - if !atomic.CompareAndSwapUint32(m.registered, 0, 1) { - return errors.New("cannot register with User Discovery when " + - "already registered") - } - data := make([]byte, 4) binary.BigEndian.PutUint32(data, 1) - obj := &versioned.Object{ Version: isRegisteredVersion, Timestamp: netTime.Now(), Data: data, } - if err := m.storage.Set(isRegisteredKey, obj); err != nil { + if err := m.kv.Set(isRegisteredKey, isRegisteredVersion, obj); err != nil { jww.FATAL.Panicf("Failed to store that the client is "+ "registered: %+v", err) } diff --git a/ud/remove.go b/ud/remove.go index 07670d7386cefbe089fd47e58fc671df212a303d..61d130ec240ad1bda693af3c39c9660f7858f597 100644 --- a/ud/remove.go +++ b/ud/remove.go @@ -2,6 +2,7 @@ package ud import ( "crypto/rand" + "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/comms/mixmessages" @@ -9,114 +10,113 @@ import ( "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" + "gitlab.com/xx_network/primitives/id" ) -type removeFactComms interface { - SendRemoveFact(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error) +// RemoveFact removes a previously confirmed fact. This will fail +// if the fact passed in is not UD service does not associate this +// fact with this user. +func (m *Manager) RemoveFact(f fact.Fact) error { + jww.INFO.Printf("ud.RemoveFact(%s)", f.Stringify()) + m.factMux.Lock() + defer m.factMux.Unlock() + return m.removeFact(f, m.comms) } -// RemoveFact removes a previously confirmed fact. Will fail if the fact is not -// associated with this client. -func (m *Manager) RemoveFact(fact fact.Fact) error { - jww.INFO.Printf("ud.RemoveFact(%s)", fact.Stringify()) - return m.removeFact(fact, m.comms) -} +// removeFact is a helper function which contacts the UD service +// to remove the association of a fact with a user. +func (m *Manager) removeFact(f fact.Fact, + rFC removeFactComms) error { -func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error { - if !m.IsRegistered() { - return errors.New("Failed to remove fact: " + - "client is not registered") + // Get UD host + udHost, err := m.getOrAddUdHost() + if err != nil { + return err } // Construct the message to send // Convert our Fact to a mixmessages Fact for sending mmFact := mixmessages.Fact{ - Fact: fact.Fact, - FactType: uint32(fact.T), + Fact: f.Fact, + FactType: uint32(f.T), } // Create a hash of our fact - fHash := factID.Fingerprint(fact) + fHash := factID.Fingerprint(f) // Sign our inFact for putting into the request - fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) + privKey := m.user.PortableUserInfo().ReceptionRSA + fSig, err := rsa.Sign(rand.Reader, privKey, hash.CMixHash, fHash, nil) if err != nil { return err } // Create our Fact Removal Request message data remFactMsg := mixmessages.FactRemovalRequest{ - UID: m.myID.Marshal(), + UID: m.e2e.GetReceptionID().Marshal(), RemovalData: &mmFact, FactSig: fSig, } - // get UD host - host, err := m.getHost() - if err != nil { - return err - } - // Send the message - _, err = rFC.SendRemoveFact(host, &remFactMsg) + _, err = rFC.SendRemoveFact(udHost, &remFactMsg) if err != nil { return err } // Remove from storage - return m.storage.GetUd().DeleteFact(fact) + return m.store.DeleteFact(f) } -type removeUserComms interface { - SendRemoveUser(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error) -} +// PermanentDeleteAccount removes the username associated with this user +// from the UD service. This will only take a username type fact, +// and the fact must be associated with this user. +func (m *Manager) PermanentDeleteAccount(f fact.Fact) error { + jww.INFO.Printf("ud.PermanentDeleteAccount(%s)", f.Stringify()) + if f.T != fact.Username { + return errors.New(fmt.Sprintf("PermanentDeleteAccount must only remove "+ + "a username. Cannot remove fact %q", f.Fact)) + } -// RemoveUser removes a previously confirmed fact. Will fail if the fact is not -// associated with this client. -func (m *Manager) RemoveUser(fact fact.Fact) error { - jww.INFO.Printf("ud.RemoveUser(%s)", fact.Stringify()) - return m.removeUser(fact, m.comms) + udHost, err := m.getOrAddUdHost() + if err != nil { + return err + } + privKey := m.user.PortableUserInfo().ReceptionRSA + + return m.permanentDeleteAccount(f, m.e2e.GetReceptionID(), privKey, m.comms, udHost) } -func (m *Manager) removeUser(fact fact.Fact, rFC removeUserComms) error { - if !m.IsRegistered() { - return errors.New("Failed to remove fact: " + - "client is not registered") - } +// permanentDeleteAccount is a helper function for PermanentDeleteAccount. +func (m *Manager) permanentDeleteAccount(f fact.Fact, myId *id.ID, privateKey *rsa.PrivateKey, + rFC removeUserComms, udHost *connect.Host) error { // Construct the message to send // Convert our Fact to a mixmessages Fact for sending mmFact := mixmessages.Fact{ - Fact: fact.Fact, - FactType: uint32(fact.T), + Fact: f.Fact, + FactType: uint32(f.T), } // Create a hash of our fact - fHash := factID.Fingerprint(fact) + fHash := factID.Fingerprint(f) // Sign our inFact for putting into the request - fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil) + fsig, err := rsa.Sign(rand.Reader, privateKey, hash.CMixHash, fHash, nil) if err != nil { return err } // Create our Fact Removal Request message data remFactMsg := mixmessages.FactRemovalRequest{ - UID: m.myID.Marshal(), + UID: myId.Marshal(), RemovalData: &mmFact, FactSig: fsig, } - // get UD host - host, err := m.getHost() - if err != nil { - return err - } - // Send the message - _, err = rFC.SendRemoveUser(host, &remFactMsg) + _, err = rFC.SendRemoveUser(udHost, &remFactMsg) // Return the error return err diff --git a/ud/remove_test.go b/ud/remove_test.go index ecf0487d68b27047953d245e5ce2710f0ec6cc45..a7cd32bc1e94347262147df7faa674d4e91aa000 100644 --- a/ud/remove_test.go +++ b/ud/remove_test.go @@ -1,110 +1,98 @@ package ud -import ( - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" - pb "gitlab.com/elixxir/comms/mixmessages" - "gitlab.com/elixxir/primitives/fact" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/comms/messages" - "gitlab.com/xx_network/crypto/csprng" - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/id" - "testing" -) - -type testRFC struct{} - -func (rFC *testRFC) SendRemoveFact(*connect.Host, *pb.FactRemovalRequest) ( - *messages.Ack, error) { - return &messages.Ack{}, nil -} - -func TestRemoveFact(t *testing.T) { - rng := csprng.NewSystemRNG() - cpk, err := rsa.GenerateKey(rng, 2048) - if err != nil { - t.Fatal(err) - } - - isReg := uint32(1) - - comms, err := client.NewClientComms(nil, nil, nil, nil) - if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Set up manager - m := &Manager{ - comms: comms, - net: newTestNetworkManager(t), - privKey: cpk, - registered: &isReg, - storage: storage.InitTestingSession(t), - myID: &id.ID{}, - } - - f := fact.Fact{ - Fact: "testing", - T: 2, - } - - // Set up storage for expected state - confirmId := "test" - if err = m.storage.GetUd().StoreUnconfirmedFact(confirmId, f); err != nil { - t.Fatalf("StoreUnconfirmedFact error: %v", err) - } - - if err = m.storage.GetUd().ConfirmFact(confirmId); err != nil { - t.Fatalf("ConfirmFact error: %v", err) - } - - tRFC := testRFC{} - - err = m.removeFact(f, &tRFC) - if err != nil { - t.Fatal(err) - } -} - -func (rFC *testRFC) SendRemoveUser(*connect.Host, *pb.FactRemovalRequest) ( - *messages.Ack, error) { - return &messages.Ack{}, nil -} - -func TestRemoveUser(t *testing.T) { - - rng := csprng.NewSystemRNG() - cpk, err := rsa.GenerateKey(rng, 2048) - if err != nil { - t.Fatal(err) - } - - isReg := uint32(1) - - comms, err := client.NewClientComms(nil, nil, nil, nil) - if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - // Set up manager - m := &Manager{ - comms: comms, - net: newTestNetworkManager(t), - privKey: cpk, - registered: &isReg, - myID: &id.ID{}, - } - - f := fact.Fact{ - Fact: "testing", - T: 2, - } - - tRFC := testRFC{} - - err = m.removeUser(f, &tRFC) - if err != nil { - t.Fatal(err) - } -} +//type testRFC struct{} +// +//func (rFC *testRFC) SendRemoveFact(*connect.Host, *pb.FactRemovalRequest) ( +// *messages.Ack, error) { +// return &messages.Ack{}, nil +//} +// +//func TestRemoveFact(t *testing.T) { +// storageSess := storage.InitTestingSession(t) +// +// kv := versioned.NewKV(ekv.Memstore{}) +// udStore, err := store.NewOrLoadStore(kv) +// if err != nil { +// t.Fatalf("Failed to initialize store %v", err) +// } +// +// // Create our Manager object +// m := &Manager{ +// services: newTestNetworkManager(t), +// e2e: mockE2e{}, +// events: event.NewEventManager(), +// user: storageSess, +// comms: mockComms{}, +// store: udStore, +// kv: kv, +// rng: fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), +// } +// f := fact.Fact{ +// Fact: "testing", +// T: 2, +// } +// +// // Set up storage for expected state +// confirmId := "test" +// if err = m.store.StoreUnconfirmedFact(confirmId, f); err != nil { +// t.Fatalf("StoreUnconfirmedFact error: %v", err) +// } +// +// if err = m.store.ConfirmFact(confirmId); err != nil { +// t.Fatalf("ConfirmFact error: %v", err) +// } +// +// tRFC := testRFC{} +// +// err = m.removeFact(f, &tRFC) +// if err != nil { +// t.Fatal(err) +// } +//} +// +//func (rFC *testRFC) SendRemoveUser(*connect.Host, *pb.FactRemovalRequest) ( +// *messages.Ack, error) { +// return &messages.Ack{}, nil +//} +// +//func TestRemoveUser(t *testing.T) { +// +// storageSess := storage.InitTestingSession(t) +// +// kv := versioned.NewKV(ekv.Memstore{}) +// udStore, err := store.NewOrLoadStore(kv) +// if err != nil { +// t.Fatalf("Failed to initialize store %v", err) +// } +// +// mockId := id.NewIdFromBytes([]byte("test"), t) +// +// // Create our Manager object +// m := &Manager{ +// services: newTestNetworkManager(t), +// e2e: mockE2e{}, +// events: event.NewEventManager(), +// user: storageSess, +// comms: mockComms{}, +// store: udStore, +// kv: kv, +// rng: fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG), +// } +// +// f := fact.Fact{ +// Fact: "testing", +// T: 2, +// } +// +// tRFC := testRFC{} +// +// udHost, err := m.getOrAddUdHost() +// if err != nil { +// t.Fatalf("getOrAddUdHost error: %v", err) +// } +// +// err = m.permanentDeleteAccount(f, mockId, &tRFC, udHost) +// if err != nil { +// t.Fatal(err) +// } +//} diff --git a/ud/search.go b/ud/search.go index ac978a438383c381973c4b4e57e174649a24d7ed..d432f47561b079ffecb335c9b999d07561a1cb7e 100644 --- a/ud/search.go +++ b/ud/search.go @@ -5,20 +5,22 @@ import ( "github.com/golang/protobuf/proto" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "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" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/factID" "gitlab.com/elixxir/primitives/fact" + "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" - "time" ) // SearchTag specifies which callback to trigger when UD receives a search // request. const SearchTag = "xxNetwork_UdSearch" -// TODO: reconsider where this comes from -const maxSearchMessages = 20 - type searchCallback func([]contact.Contact, error) // Search searches for the passed Facts. The searchCallback will return a list @@ -26,8 +28,13 @@ type searchCallback func([]contact.Contact, error) // 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) +func Search(services CMix, events event.Reporter, + rng csprng.Source, grp *cyclic.Group, + udContact contact.Contact, callback searchCallback, + list fact.FactList, + params single.RequestParams) ([]id.Round, + receptionID.EphemeralIdentity, error) { + jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), params.Timeout) factHashes, factMap := hashFactList(list) @@ -35,73 +42,81 @@ func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout ti request := &SearchSend{Fact: factHashes} requestMarshaled, err := proto.Marshal(request) if err != nil { - return errors.WithMessage(err, "Failed to form outgoing search request.") - } - - f := func(payload []byte, err error) { - m.searchResponseHandler(factMap, callback, payload, err) + return []id.Round{}, receptionID.EphemeralIdentity{}, + errors.WithMessage(err, "Failed to form outgoing search request.") } - // get UD contact - c, err := m.getContact() - if err != nil { - return err + response := searchResponse{ + cb: callback, + services: services, + events: events, + grp: grp, + factMap: factMap, } - err = m.single.TransmitSingleUse(c, requestMarshaled, SearchTag, - maxSearchMessages, f, timeout) + rndId, ephId, err := single.TransmitRequest(udContact, SearchTag, requestMarshaled, + response, params, services, rng, grp) if err != nil { - return errors.WithMessage(err, "Failed to transmit search request.") + return []id.Round{}, receptionID.EphemeralIdentity{}, + errors.WithMessage(err, "Failed to transmit search request.") } - if m.client != nil { - m.client.ReportEvent(1, "UserDiscovery", "SearchRequest", + if events != nil { + events.Report(1, "UserDiscovery", "SearchRequest", 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 + events event.Reporter + 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.client != nil { - m.client.ReportEvent(1, "UserDiscovery", "SearchResponse", - fmt.Sprintf("Received: %+v", searchResponse)) + if m.events != nil { + m.events.Report(1, "UserDiscovery", "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 @@ -124,10 +139,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)) - // Convert each contact message into a new contact.Contact for i, c := range response { // Unmarshal user ID bytes @@ -142,7 +156,7 @@ func (m *Manager) parseContacts(response []*Contact, // Create new Contact contacts[i] = contact.Contact{ ID: uid, - DhPubKey: m.grp.NewIntFromBytes(c.PubKey), + DhPubKey: grp.NewIntFromBytes(c.PubKey), Facts: facts, } diff --git a/ud/search_test.go b/ud/search_test.go index 9ba8651529d1b0666bf7bdb9c268941fc7638551..b9bcf5b09b7b77e3c452391317df5c6fc189151b 100644 --- a/ud/search_test.go +++ b/ud/search_test.go @@ -1,106 +1,100 @@ package ud import ( - "fmt" - "github.com/golang/protobuf/proto" - "github.com/pkg/errors" + "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/single" - "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" - "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/contact" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/factID" "gitlab.com/elixxir/primitives/fact" - "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id" - "math/rand" - "reflect" - "strings" "testing" "time" ) // Happy path. func TestManager_Search(t *testing.T) { - // Set up manager - isReg := uint32(1) - - comms, err := client.NewClientComms(nil, nil, nil, nil) + // Set up mock UD values + grp := getGroup() + prng := NewPrng(42) + privKeyBytes, err := csprng.GenerateInGroup( + grp.GetP().Bytes(), grp.GetP().ByteLen(), prng) if err != nil { - t.Errorf("Failed to start client comms: %+v", err) - } - - store := storage.InitTestingSession(t) - - m := &Manager{ - comms: comms, - storage: store, - net: newTestNetworkManager(t), - grp: store.E2e().GetGroup(), - single: &mockSingleSearch{}, - registered: &isReg, + t.Fatalf("Failed to generate a mock private key: %v", err) } + udMockPrivKey := grp.NewIntFromBytes(privKeyBytes) + // Set up mock manager + m, tnm := newTestManager(t) // Generate callback function - callbackChan := make(chan struct { - c []contact.Contact - err error - }) + callbackChan := make(mockChannel) callback := func(c []contact.Contact, err error) { - callbackChan <- struct { - c []contact.Contact - err error - }{c: c, err: err} + callbackChan <- mockResponse{ + c: c, + err: err} } // 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)), - }) + udbId, err := id.Unmarshal(tnm.instance.GetFullNdf().Get().UDB.ID) + if err != nil { + t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err) } - factHashes, _ := hashFactList(factList) + factList = append(factList, fact.Fact{ + Fact: udbId.String(), + T: fact.Username, + }) + + grp = getGroup() 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}, - }) + udContact, err := m.GetContact() + if err != nil { + t.Fatalf("Failed to get ud contact: %v", err) } + contacts = append(contacts, &Contact{ + UserID: udContact.ID.Bytes(), + PubKey: udContact.DhPubKey.Bytes(), + }) + + // Generate a mock UD service to respond to the search request + + receiver := newMockReceiver(callbackChan, contacts, t) - err = m.Search(factList, callback, 10*time.Millisecond) + mockListener := single.Listen(SearchTag, udbId, udMockPrivKey, + tnm, grp, receiver) + defer mockListener.Stop() + + timeout := 100 * time.Millisecond + p := single.RequestParams{ + Timeout: timeout, + MaxResponseMessages: 1, + CmixParam: cmix.GetDefaultCMIXParams(), + } + + _, _, err = Search(m.network, m.events, prng, m.e2e.GetGroup(), + udContact, callback, factList, p) if err != nil { - t.Errorf("Search() returned an error: %+v", err) + t.Fatalf("Search() returned an error: %+v", err) } // Verify the callback is called select { case cb := <-callbackChan: if cb.err != nil { - t.Errorf("Callback returned an error: %+v", cb.err) - } - - c, err := m.getContact() - if err != nil { - t.Errorf("Failed to get UD contact: %+v", err) + t.Fatalf("Callback returned an error: %+v", cb.err) } - expectedContacts := []contact.Contact{c} + expectedContacts := []contact.Contact{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) } - case <-time.After(100 * time.Millisecond): + case <-time.After(timeout): t.Error("Callback not called.") } } -// +// todo; note this was commented out in release // // Error path: the callback returns an error. // func TestManager_Search_CallbackError(t *testing.T) { // isReg := uint32(1) @@ -168,6 +162,7 @@ func TestManager_Search(t *testing.T) { // // } // } // +// todo; note this was commented out in release // // Error path: the round event chan times out. // func TestManager_Search_EventChanTimeout(t *testing.T) { // isReg := uint32(1) @@ -225,324 +220,3 @@ func TestManager_Search(t *testing.T) { // // 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))} - - 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 - 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) - - 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(&SearchResponse{Contacts: contacts}) - if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) - } - - m.searchResponseHandler(factMap, callback, payload, nil) - - select { - case results := <-callbackChan: - if results.err != nil { - t.Errorf("Callback returned an error: %+v", results.err) - } - if !reflect.DeepEqual(expectedContacts, results.c) { - t.Errorf("Callback returned incorrect Contacts."+ - "\nexpected: %+v\nreceived: %+v", expectedContacts, results.c) - } - 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} - } - - 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: 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 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 - }) - callback := func(c []contact.Contact, err error) { - callbackChan <- struct { - c []contact.Contact - err error - }{c: c, err: err} - } - - var contacts []*Contact - for i := 0; i < 10; i++ { - contacts = append(contacts, &Contact{ - UserID: []byte{byte(i + 1)}, - }) - } - - // Generate expected Send message - payload, err := proto.Marshal(&SearchResponse{Contacts: contacts}) - if err != nil { - t.Fatalf("Failed to marshal LookupSend: %+v", err) - } - - m.searchResponseHandler(nil, callback, payload, nil) - - select { - 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.") - } -} - -// 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)), - } - 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 !reflect.DeepEqual(expectedHashMap, hashMap) { - t.Errorf("hashFactList() failed to return the expected hash map."+ - "\nexpected: %+v\nreceived: %+v", expectedHashMap, hashMap) - } -} - -// Happy path. -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) - - 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)]}, - }) - } - - testContacts, err := m.parseContacts(contacts, factMap) - if err != nil { - t.Errorf("parseContacts() returned an error: %+v", err) - } - - if !reflect.DeepEqual(expectedContacts, testContacts) { - t.Errorf("parseContacts() did not return the expected contacts."+ - "\nexpected: %+v\nreceived: %+v", expectedContacts, testContacts) - } -} - -func TestManager_parseContacts_username(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) - - var contacts []*Contact - var expectedContacts []contact.Contact - for i, hash := range factHashes { - contacts = append(contacts, &Contact{ - UserID: id.NewIdFromString("user", id.User, t).Marshal(), - Username: "zezima", - 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{{"zezima", fact.Username}, factMap[string(hash.Hash)]}, - }) - } - - testContacts, err := m.parseContacts(contacts, factMap) - if err != nil { - t.Errorf("parseContacts() returned an error: %+v", err) - } - - if !reflect.DeepEqual(expectedContacts, testContacts) { - t.Errorf("parseContacts() did not return the expected contacts."+ - "\nexpected: %+v\nreceived: %+v", expectedContacts, testContacts) - } -} - -// 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) - } -} - -// mockSingleSearch is used to test the search function, which uses the single- -// use manager. It adheres to the SingleInterface interface. -type mockSingleSearch struct { -} - -func (s *mockSingleSearch) TransmitSingleUse(partner contact.Contact, payload []byte, - _ string, _ uint8, callback single.ReplyCallback, _ time.Duration) error { - - searchMsg := &SearchSend{} - if err := proto.Unmarshal(payload, searchMsg); err != nil { - return errors.Errorf("Failed to unmarshal SearchSend: %+v", err) - } - - 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) - } - - callback(msg, nil) - return nil -} - -func (s *mockSingleSearch) StartProcesses() (stoppable.Stoppable, error) { - return stoppable.NewSingle(""), nil -} diff --git a/storage/ud/facts.go b/ud/store/facts.go similarity index 85% rename from storage/ud/facts.go rename to ud/store/facts.go index 9762bc068b915c4d5138226bf827157911508332..202d8814bf5748ad5c7f8bf77c68ca26344fa842 100644 --- a/storage/ud/facts.go +++ b/ud/store/facts.go @@ -10,42 +10,36 @@ package ud import ( "fmt" "github.com/pkg/errors" - "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/primitives/fact" - "sync" ) +// Error constants to return up the stack. 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" + factNotInStoreErr = "Fact %v does not exist in store" + statefulStoreErr = "cannot overwrite ud store with existing data" ) -// 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 -} +// RestoreFromBackUp initializes the confirmedFacts map +// with the backed up fact data. This will error if +// the store is already stateful. +func (s *Store) RestoreFromBackUp(backupData fact.FactList) error { + s.mux.Lock() + defer s.mux.Unlock() -// NewStore creates a new, empty Store object. -func NewStore(kv *versioned.KV) (*Store, error) { - kv = kv.Prefix(prefix) + if len(s.confirmedFacts) != 0 || len(s.unconfirmedFacts) != 0 { + return errors.New(statefulStoreErr) + } - s := &Store{ - confirmedFacts: make(map[fact.Fact]struct{}, 0), - unconfirmedFacts: make(map[string]fact.Fact, 0), - kv: kv, + for _, f := range backupData { + if !isFactZero(f) { + s.confirmedFacts[f] = struct{}{} + } } - return s, s.save() + return s.save() } // StoreUnconfirmedFact stores a fact that has been added to UD but has not been @@ -94,10 +88,6 @@ 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) diff --git a/storage/ud/facts_test.go b/ud/store/facts_test.go similarity index 81% rename from storage/ud/facts_test.go rename to ud/store/facts_test.go index c1f5dfc5c965d8b1bece3ab7fa3b903dc21e2b33..332b5289da335390d76df7f41adbb49f2bd2d975 100644 --- a/storage/ud/facts_test.go +++ b/ud/store/facts_test.go @@ -18,7 +18,7 @@ import ( func TestNewStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) _, err := NewStore(kv) if err != nil { @@ -27,8 +27,68 @@ func TestNewStore(t *testing.T) { } +// Unit test +func TestStore_RestoreFromBackUp(t *testing.T) { + + kv := versioned.NewKV(ekv.MakeMemstore()) + + s, err := NewStore(kv) + if err != nil { + t.Errorf("NewStore() produced an error: %v", err) + } + + expected := fact.Fact{ + Fact: "josh", + T: fact.Username, + } + + fl := fact.FactList{expected} + + err = s.RestoreFromBackUp(fl) + if err != nil { + t.Fatalf("RestoreFromBackup err: %v", err) + } + + _, exists := s.confirmedFacts[expected] + if !exists { + t.Fatalf("Fact %s does not exist in map", expected) + } + +} + +// Error case. +func TestStore_RestoreFromBackUp_StatefulStore(t *testing.T) { + + kv := versioned.NewKV(ekv.MakeMemstore()) + + s, err := NewStore(kv) + if err != nil { + t.Errorf("NewStore() produced an error: %v", err) + } + + confirmId := "confirm" + expected := fact.Fact{ + Fact: "josh", + T: fact.Username, + } + + err = s.StoreUnconfirmedFact(confirmId, expected) + if err != nil { + t.Fatalf("StoreUnconfirmedFact error: %v", err) + } + + // Expected error: should error when restoring on + // a stateful store. + fl := fact.FactList{expected} + err = s.RestoreFromBackUp(fl) + if err == nil { + t.Fatalf("RestoreFromBackup err: %v", err) + } + +} + func TestStore_ConfirmFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -66,7 +126,7 @@ func TestStore_ConfirmFact(t *testing.T) { } func TestStore_StoreUnconfirmedFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -94,7 +154,7 @@ func TestStore_StoreUnconfirmedFact(t *testing.T) { } func TestStore_DeleteFact(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -126,7 +186,7 @@ func TestStore_DeleteFact(t *testing.T) { } func TestStore_BackUpMissingFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -161,7 +221,7 @@ func TestStore_BackUpMissingFacts(t *testing.T) { } func TestStore_BackUpMissingFacts_DuplicateFactType(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -198,7 +258,7 @@ func TestStore_BackUpMissingFacts_DuplicateFactType(t *testing.T) { } func TestStore_GetFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) testStore, err := NewStore(kv) if err != nil { @@ -247,7 +307,7 @@ func TestStore_GetFacts(t *testing.T) { } func TestStore_GetFactStrings(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) testStore, err := NewStore(kv) if err != nil { diff --git a/storage/ud/store.go b/ud/store/store.go similarity index 89% rename from storage/ud/store.go rename to ud/store/store.go index 45b7ffdc4e1e516445e0761f7bad08b9ca666e0c..0cf274add91a8d08791e7b0c189b8d190d0f7962 100644 --- a/storage/ud/store.go +++ b/ud/store/store.go @@ -10,6 +10,7 @@ import ( "gitlab.com/xx_network/primitives/netTime" "io/fs" "strings" + "sync" ) // Storage constants @@ -30,12 +31,29 @@ const ( saveConfirmedFactErr = "Failed to save confirmed facts" ) -// 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 +// 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 +} + +// NewStore creates a new, empty Store object. +func NewStore(kv *versioned.KV) (*Store, error) { + kv = kv.Prefix(prefix) + + s := &Store{ + confirmedFacts: make(map[fact.Fact]struct{}, 0), + unconfirmedFacts: make(map[string]fact.Fact, 0), + kv: kv, + } + + return s, s.save() } ///////////////////////////////////////////////////////////////// @@ -180,6 +198,14 @@ func (s *Store) loadUnconfirmedFacts() error { // 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) { diff --git a/storage/ud/store_test.go b/ud/store/store_test.go similarity index 94% rename from storage/ud/store_test.go rename to ud/store/store_test.go index ffe058f51c7b3cf41799c28207eda5253a0ab8ad..ed56cb67c2a320cea8073e3baa8287998a65763f 100644 --- a/storage/ud/store_test.go +++ b/ud/store/store_test.go @@ -11,7 +11,7 @@ import ( // Test it loads a Store from storage if it exists. func TestNewOrLoadStore_LoadStore(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -34,7 +34,7 @@ func TestNewOrLoadStore_LoadStore(t *testing.T) { // 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)) + kv := versioned.NewKV(ekv.MakeMemstore()) receivedStore, err := NewOrLoadStore(kv) if err != nil { @@ -57,7 +57,7 @@ func TestNewOrLoadStore_NewStore(t *testing.T) { } func TestStore_MarshalUnmarshal_ConfirmedFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { @@ -93,7 +93,7 @@ func TestStore_MarshalUnmarshal_ConfirmedFacts(t *testing.T) { } func TestStore_MarshalUnmarshal_UnconfirmedFacts(t *testing.T) { - kv := versioned.NewKV(make(ekv.Memstore)) + kv := versioned.NewKV(ekv.MakeMemstore()) expectedStore, err := NewStore(kv) if err != nil { diff --git a/ud/utils_test.go b/ud/utils_test.go index 7d3b3626c3b479e0a14d87746ac707f970a7327e..675acc924c0ee015c8c25ad1bfe0af3f244d77f7 100644 --- a/ud/utils_test.go +++ b/ud/utils_test.go @@ -15,97 +15,415 @@ package ud import ( + "bytes" + "github.com/golang/protobuf/proto" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/event" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage/user" + "gitlab.com/elixxir/client/storage/versioned" + store "gitlab.com/elixxir/client/ud/store" + pb "gitlab.com/elixxir/comms/mixmessages" + "gitlab.com/elixxir/comms/testkeys" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/ekv" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/messages" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/crypto/large" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/utils" + "io" + "math/rand" "testing" "time" - "gitlab.com/elixxir/client/cmix/gateway" - "gitlab.com/elixxir/client/event" - "gitlab.com/elixxir/client/interfaces" - "gitlab.com/elixxir/client/interfaces/message" - "gitlab.com/elixxir/client/interfaces/params" - "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/comms/network" - "gitlab.com/elixxir/crypto/e2e" - "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" - "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/ndf" ) -func newTestNetworkManager(t *testing.T) interfaces.NetworkManager { +// Base64 encoded dh private key using a pre-seeded prng +// prng := NewPrng(42) +const dhPrivKeyEnc = `U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVLf15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAg/YdN1vAK0HfT5GSnhj9qeb4LlTnSOgeeeS71v40zcuoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhaXiu0M48bE8657w+BJW1cS/v2+DBAoh+EA2s0tiF9pLLYH2gChHBxwceeWotwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGFJiUf980RBDtTBFgI/qONXa2/tJ/+JdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP1m10/sHhuJSRq3I25LdSFikM8r60LDyicyhWDxqsBnzqbov0bUqytGgEAsX7KCDohdMmDx3peCg9Sgmjb5bCCUF0bj7U2mRqmui0+ntPw6ILr6GnXtMnqGuLDDmvHP0rO1EhnqeVM6v0SNLEedMmB1M5BZFMjMHPCdo54Okp0C` + +const searchMessageEnc = `jz4ylDFSElBrwy4pKrDlo1lbxCiHo2KVVATS4QHsJng4DPfK4iaXZIxhNO0qWwXD0z1s903N5hl5aeNY+nauf1IfUy9CVDaCRyYylpGDem+cxH+qnowpDQEwQGgVYgaqYS9O3D1XMCxpnNhdSL7r1kVmb/VvsXaXyThisA177HHVAvzpH0nmg9RHnYCyLrLa+WYaJKwqi0qdJ0i5nVkBUqS70rKHNMaPb8aqKaPr4dO+es8Sw7gO2iOu3n3oVa8YeyxOvsPLhZzyeyVPHy1Ee2o6iaUKJeXpT/SlsGpwDJ0n1mhwlUI/qG1oxFaDuKRtJV571Er1afoifnCEUUIOsf7myT3awtm8TpRfIrd8PglXxKnKm9RdQrUZoU+6eLhVQ86ZPF7mfskMDQw/1Yo2pGpZjzoFGUGI2GnkWD06rsa2u+nHHK6laDX/gSs8zmgm4kA1qSPCOtb9f3y4QFLq49xZU+xyWH3iWGrsgSw9nnhO7hUj8R1mftBzFc/m9pHY4BfZAIRBIvV1HdKoOrMwEbMoGxjnHCobraFVqt2nR1wqkhHysU9TWUFzfQ+ameJ9XoKr+TDhWIltmBVlzD0QdfR4jixOtUEvpJN7ewEQYEukg+GVJRWFa+J+nEsJzVsX/bCasWl/tWy9HZEj9NPonIlmsTic1wL7b+bJ6feurvSbkVpzcKkATDMHTUR7e1Z/qoRPgVLHAKxFjgVTZ3zxOQOOwXUUax7HiXvA2f+XIE/bHdisx9bE+/zpR6PRvOXkAqAfFdCfV5eeg4kfwL9FDahtUZ0mYd9Ywngp0lCk/tsYQrxx3v4WM9hsGHId6Ic8c3fRFiAcJgVWTHCbt+Nj2SGa80zwJE1FvNQc5ia2csY8PgthQuOYuekR+M1IBOsiVKaw9qAR3uGUP/fEC857srAEicC4EPTv1NwsESQ8V1Ar9ZDTlB3RnmYNBR1JGf7CTqN0teNREQ/g8hLa77rkoEyFZcibQDNCr/LpgDwtSjIxHXoLKzQ3Caj2+1Jo4BWP2X5oUjuej757NfXdLTbkNHLv9soVe8V1/poKAB7pmqDh8dzvva2GLFwBXr+IOCpv1VdWbuxd/2CtWUvz/yov5PeeqwX07Nz4T3RrRZB/xGQO84ektOMdFcGG8PG7hGNAm7QpEkb9dzbhmzqbi3kLUWkHZM4DKOUsIB33jL41QoY5zV504aLTfI8l5W7AAEfr8FzAI1TY32JtCnCG/ZU1ianUFKUi9Awi1njBNI5OJ596dqGsYNoJh2QxitfmSOXpcC0S5vUP4ltAvgE7etRvm1koGqttnyb6TvBAfIFbSjmd2E9rYlkraK1DhjL+6lbLQu1zeSGbTDeWri5/2wVBf2F3MiYjxqB5UzejYdcwvVv+hAyZaBERnkQyxnC6C1uBcR8w1Yxi+jocPr8//TtPoC2HUGUa/7ZfJU8VupjNf5BYXOvRIbGEptHBsHlt/xJOOEy9gsoCdmqxvMD16MTOo7geIUt77TaxJVnz9eSN6xAXrhvK41X0mxd93UA21gYt9E33NHW/wqAvnZR2AKhtaZaJL+9RhhMGMZD33IMSOQRwovlZ3INAGrKEHe8+j+eEvPeh2pkmzaKC8eyNEzaRLt4Mrncqdh8Ie17aeYxzScUqD7N5uLtJtkOeZTknoJvVRTOGEIFlt+WJR1nUr3EjGwrJxaXsqRedMVhDFwd8gwUIaFGVAbpYkH8GrbSrM5KFHEzerByx+/a1VfiPG2g/UqMqLnjHyKRqTppGiaQdtEFitMvlFdxdRDV0ql9UIckKynzfLWZbh83RbtcM34rEozR39y6WdAXiQBEmvjSy0NmrLmwNcDu1ICA0Mfm6WcD2iPmxT2ynZrv2b+GEVRVdEEH6+WohMWzjMsAjOD0H1mB8bT6hJDCPLjgcKbKPd0lTCH49LvWQi4E3UJHfGn4G0nLNuxlLVYbOXrp5wI3cKmDvnld7kfyf6T45XH2i+NUQnir30LSGHuMp3DANmyybsYzUaOOFopWIYIwj5rSsDQ7uKXw3Ym+3f6fKSZRedZS2vHFZqx5KNq7hz9EDcQ2K5VIiW1UIpgJFdeUFT8fiRmuVE/JuafB+pyjci0UuOk8r4BzihDAkK1CBhh42qtEjFnhnszDzLln3PKbrIDG2GvokEGnzVP+iTFsX4TxK3GoiZCJo9BLpoggco7LbX8BgI+6eZDzcdgaNZybRl63shquIOyUDbrRTP9bifB/0Vd6dCTHQ5ypsPCehhvqqJA+cP4Za83lovBP4TLfXMTtdIcW/dxiUY1hZ0/RCcPKldzvBdVY6x7paqC8H+L/lBN/okI87GoZbRoIVd4NvPBzPUvD/MB4Ke8Mw+zXLKBBC1bHq06IMdqNZMTGLdNbDuQ8a23NZVv39HZ7+6Y0VumatfxUgtnHywCpUV6Ob2XBBjtXHNxFofmfov4xogxjjuS2eiKzUb8r9okVwja6mQPAqFFwMbo6xLWkDonvQ9WLhvF8uDAQcFBvlk46L9LuxyJtMlhtrMnRgdYJxu4CPYXKOhliyzkpeJETXzY10DOoNUTxZVfktoXplwxhSI3p7N4N3+hhOUcLFXdjvLKTvSRldREH1TcNVvl/uuqZPQUDlgIb9fKWo2XH6PgwMv1U/YNHQnuQKBm3BfYbD58kLXxK/9Q01mfpMGOEQLYgvFktUc4jbDqwnBKvF9E1IeMY0Vk11/JHLivLnq8OO90satyevQwBQELRblZxbUMF715kF/XTz/3N2GklPkDCZx9D9axGKjHIzYxl6odd++LdIxd6trVYM2eWwm8D9qJO/emT0C8HApnYknOsuw5Szj0IEzuXugPNqXWTl2mlSYLCOHx+nhrNij6YF+vb7N9VC4wFp71icJ7rFOV65XkzqP7FHjhGUdPdxWMETb+tBHatuGB3Rgf/zhxPhP/V9Dya9iTbrC1eLVVAAgQZJK4UzJcGPa78Jto9/0TCil0mbx48f3bS5mKqWAqaWDR24mdaQVU1hkYX0PLinVH/vHd0gvrvKKMXfktMg4g9KY7eHzbrCTP5/QU8MUNFqczkx6/B90As0LxlKnB3sBjxdq5orxSiELu/z6JADEjwNh7F2IUnOnVKeyGtnhY0hZsyGP8Mr05R9dvLQcKQXJ2hgYIDetqDV/Xk/lZkyHWTp1tJSdQBDGCoOMBpY0MjkKWch+f4SdQrI0sSkxHgrznYjW7zwdNAhjB7h6jfOzK27oxR4mYoLsq7VMzIaARTM+7teY8D1/unaGvjt+yJAX03mQkntEAPeF/H7KrA2zZIF+ncI95AfSmJMLl2gBkFBcPJtBQJ6eVn8Ol6JvkKG2NHoBaIIMNuqO55b1w+to1RvrC/HilT7crve7PDczaGRxDVRupgF5xGsO61Dod0QvipS5a54fv6ZTgQ11gjhYhBEeLFewphnKOf7ReaZBgtiiwgUmJVhRxJFAVWzM9jsNZq9RHEmbZzVEh0tiqjRBW7ZSiGndHKw2PHHiQ2psFurs0R1W2HVs9/cMPbQgZvgr2P4+cHE+Igyr2UdNdsFIBF9Q02llG7pZivz5qEQktrOe2IGt9gn7b4ReAVr7zgZKWkFUmu4XYXOWzrwpyFeWnACDnzwmhSSBqlMlC1IiH/BkebbvZFD7W5B+di2tCOW6NXGXe/X+J3fGWJdELkcDnN7UnY6YMZvz0ij+fHJoXecjefsZsXLDqFjX862krUK2CmeipNk9pSnur4OohiHASO/SYQyoQCdRr2wNWqPr6444En4yTSyCcq1j5beytd4xuT5tXxXEdHyUO9CjgnjeNVD/bNW4eYbR5T8sA4gM8WjTq8VzyOUqpWeHmEicE/EYt0p/rmt+SxbCAZuoAasIK3ndsPYzNx4sZngp6Gx7mzv1ydEKSGdC9JxOGS/n92nv+J5RB8VHJIJp/oGZNGl9dM3h7I8kq+XkSyrBud5RkvHgE40cfITkW0q6e0lXJ3LPN+m6Da3/chdKuhy0e2q+Rms9oQOuIpayCF37LR7XtlAfZZIIyqBah7v/4YyEZd2JE2E+mCrcQctY1TuZMA3mar6qE3tTnIHn3sdnJJZy36/9S45R1sppJKLXDyDP8BzKu/ABDQAeJ+ZI3uovRDBjMDmfbcvRVXbCZ+TwacJgWvw+jT4qyllLDMZ1xhDaFUsaohXfOM1xvkr6uSSoiAX8aLI5aQwQDy7IqasdEx3r1sizxZ77vBFE+rsjnOK5+zZdPgVSDabe2oBUFIBT7leopmuRA97pdXJrKittS91nOKmFpVLHrCjKhnmLVmR0zajRUaifjQAuxhY9kE0+P5Wydqo3ByYBoyVBuOsyg6EkXXmFoxdVOxXsThUerHyimD6LRVy7uLBnBDlVhODfCgO9ygyTYzno54SXKCDKP+QiiCdQ1iZvVylL4jooNBCSqFLBKx4m26twQuke7I69tETwrz6+U6/FS6CyC6vXa6pxFXW3E5zYKEylEyr9jtjw3sQODIEuPxNpqLuGSrojMr55wqeJYeE3Wo2AgQfbeDOgRWwN4Nz8StUYYQiVizxsMD9cRKmkaG9+p1Oq4NgkSE9nSSre5zuRY1o/Uo4nn3jjPTnMzmRxFARg9Ldk2YT6bndacf7EHQc54ZEEOJON2pRpqqge6aPriMI/JSvJuT4880lgRvI3bbvmxlqFMXaT0YmOk60qrWklW2s5fSbuVp8lW8/kC0VxTuT7p602gdiKYsKgubAI+CMY/ByLAKNnz6Edm6Sc9ZT+yOVw87JOGFTbl/dOE6JPlUss08/axMP/HCagtXgKKL3F7ZBiqmqPtLos90g6DOLZcZsf0gzi1bXvPnYzvmWbhQWLUaGi0TI9ADUwosOEr9kZ/VzNrA8VNDjdPxIDu2qIUPM3Tfa8Shd6EG07qxyIK4E0pLqrLZ8F6BUBmq3xQzzInFJ294K` +const dhKeyEnc = `hQj4FKeaQDt34eO/BQe5OSk571WMQBu4YNqMQ0TFeuzjzCM9lLMuOsQhcyjZcIo4qanfv0egkwW7YgSAAt+WaOc6S1rzbxidb6v00KCEVkXAl3NSYk3ms8Jp0uzejOKEI6I3TCjX+71GZ6304xRKQSXDnuYG7ypvx+tYgj6EWx/sh5DsdIyT0KVxo9c/msT0cT6Fq1Hz1S3TkQc2dLcamn86gtKs4cSNuG/b7HFvyKSCK4KqQ2wrQWhiJCh0L6UhXEqTVwyoVgjH8pA/R+eTIE3QL4rDPy+gFdaZOVbr2jOOEvgYTJxWU7VPygXkPtTTHL8eD5IXoj9b46ll6CAHC5xwkCfiqO+HLE2Cg6nVmIq7eCZOLH1WnrYcjv7TQrYNCdb2hGDZznbI2wVswiIW7CilHaKH5KmiVfZG3cPKfwcTATRFR5oNVcav4wikObzB7gAmP57lGTwF1WqfLlGAxDF1R+Qiljfp1U1f0s7MBjG62gFwxpxs4AgA8lZDnvlRgeM1P0zsaypUbrHXFhu6` +const lookupRequestEnc = `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` + +func newTestManager(t *testing.T) (*Manager, *testNetworkManager) { + + keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath()) + if err != nil { + t.Fatalf("Could not load private key: %v", err) + } + + key, err := rsa.LoadPrivateKeyFromPem(keyData) + if err != nil { + t.Fatalf("Could not load public key") + } + + kv := versioned.NewKV(ekv.MakeMemstore()) + udStore, err := store.NewOrLoadStore(kv) + if err != nil { + t.Fatalf("Failed to initialize store %v", err) + } + + // Create our Manager object + m := &Manager{ + e2e: mockE2e{}, + events: event.NewEventManager(), + user: mockUser{testing: t, key: key}, + store: udStore, + comms: &mockComms{}, + kv: kv, + } + tnm := newTestNetworkManager(t) + m.network = tnm + + netDef := m.network.GetInstance().GetPartialNdf().Get() + // Unmarshal UD ID from the NDF + udID, err := id.Unmarshal(netDef.UDB.ID) + if err != nil { + t.Fatalf("failed to "+ + "unmarshal UD ID from NDF: %+v", err) + } + + params := connect.GetDefaultHostParams() + params.AuthEnabled = false + params.SendTimeout = 20 * time.Second + + // Add a new host and return it if it does not already exist + _, err = m.comms.AddHost(udID, netDef.UDB.Address, + []byte(netDef.UDB.Cert), params) + if err != nil { + t.Fatalf("User Discovery host " + + "object could not be constructed.") + } + + udContact, err := m.GetContact() + if err != nil { + t.Fatalf("Failed to get contact: %v", err) + } + + tnm.c = udContact + + return m, tnm +} + +// Prng is a PRNG that satisfies the csprng.Source interface. +type Prng struct{ prng io.Reader } + +func NewPrng(seed int64) csprng.Source { return &Prng{rand.New(rand.NewSource(seed))} } +func (s *Prng) Read(b []byte) (int, error) { return s.prng.Read(b) } +func (s *Prng) SetSeed([]byte) error { return nil } +func newTestNetworkManager(t *testing.T) *testNetworkManager { instanceComms := &connect.ProtoComms{ Manager: connect.NewManagerTesting(t), } - thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), - getNDF(), nil, nil, t) + getNDF(), getGroup(), getGroup(), t) if err != nil { t.Fatalf("Failed to create new test instance: %v", err) } - return &testNetworkManager{ - instance: thisInstance, + tnm := &testNetworkManager{ + instance: thisInstance, + testingFace: t, } + + return tnm } -// testNetworkManager is a test implementation of NetworkManager interface. -type testNetworkManager struct { - instance *network.Instance +func getGroup() *cyclic.Group { + return cyclic.NewGroup( + large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A"+ + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D"+ + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615"+ + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC"+ + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C"+ + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2"+ + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE"+ + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E"+ + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF"+ + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323"+ + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C"+ + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63"+ + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3"+ + "5873847AEF49F66E43873", 16), + large.NewIntFromString("2", 16)) } -func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Single) ([]id.Round, e2e.MessageID, time.Time, error) { - return nil, e2e.MessageID{}, time.Time{}, nil +type mockUser struct { + testing *testing.T + key *rsa.PrivateKey } -func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) { - return nil, nil +func (m mockUser) PortableUserInfo() user.Info { + + return user.Info{ + TransmissionID: id.NewIdFromString("test", id.User, m.testing), + TransmissionSalt: []byte("test"), + TransmissionRSA: m.key, + ReceptionID: id.NewIdFromString("test", id.User, m.testing), + ReceptionSalt: []byte("test"), + ReceptionRSA: m.key, + Precanned: false, + RegistrationTimestamp: 0, + E2eDhPrivateKey: getGroup().NewInt(5), + E2eDhPublicKey: getGroup().NewInt(6), + } } -func (tnm *testNetworkManager) GetVerboseRounds() string { - return "" +func (m mockUser) GetReceptionRegistrationValidationSignature() []byte { + return []byte("test") } -func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) { +type mockReceiver struct { + responses []*Contact + c mockChannel + t *testing.T +} + +func newMockReceiver(c mockChannel, response []*Contact, t *testing.T) *mockReceiver { + return &mockReceiver{ + c: c, + t: t, + responses: response, + } +} + +func (receiver *mockReceiver) Callback(req *single.Request, + _ receptionID.EphemeralIdentity, _ []rounds.Round) { + if req.GetTag() == SearchTag { + response := &SearchResponse{} + response.Contacts = receiver.responses + + responsePayload, err := proto.Marshal(response) + if err != nil { + receiver.t.Fatalf("Failed to marshal response message: %v", err) + } + + _, err = req.Respond(responsePayload, + cmix.GetDefaultCMIXParams(), 100*time.Millisecond) + if err != nil { + receiver.t.Fatalf("Respond error: %v", err) + } + } else if req.GetTag() == LookupTag { + response := &LookupResponse{ + PubKey: receiver.responses[0].PubKey, + Username: receiver.responses[0].Username, + } + + responsePayload, err := proto.Marshal(response) + if err != nil { + receiver.t.Fatalf("Failed to marshal response message: %v", err) + } + + _, err = req.Respond(responsePayload, + cmix.GetDefaultCMIXParams(), 100*time.Millisecond) + if err != nil { + receiver.t.Fatalf("Respond error: %v", err) + } + + } + +} + +// testNetworkManager is a mock implementation of the CMix interface. +type testNetworkManager struct { + requestProcess message.Processor + instance *network.Instance + testingFace interface{} + c contact.Contact + responseProcessor message.Processor +} + +func (tnm *testNetworkManager) Send(recipient *id.ID, fingerprint format.Fingerprint, + service message.Service, + payload, mac []byte, cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) { + msg := format.NewMessage(tnm.instance.GetE2EGroup().GetP().ByteLen()) + // Build message. Will panic if inputs are not correct. + msg.SetKeyFP(fingerprint) + msg.SetContents(payload) + msg.SetMac(mac) + msg.SetSIH(service.Hash(msg.GetContents())) + // If the recipient for a call to Send is UD, then this + // is the request pathway. Call the UD processor to simulate + // the UD picking up the request + if bytes.Equal(tnm.instance.GetFullNdf(). + Get().UDB.ID, + recipient.Bytes()) { + tnm.responseProcessor.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{}) + + } else { + // This should happen when the mock UD service Sends back a response. + // Calling process mocks up the requester picking up the response. + tnm.requestProcess.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{}) + } + return 0, ephemeral.Id{}, nil } -func (tnm *testNetworkManager) SendManyCMIX([]message.TargetedCmixMessage, params.CMIX) (id.Round, []ephemeral.Id, error) { - return 0, nil, nil +func (tnm *testNetworkManager) AddFingerprint(identity *id.ID, + fingerprint format.Fingerprint, mp message.Processor) error { + // AddFingerprint gets called in both the request and response + // code-paths. We only want to set in the code-path transmitting + // from UD + if !bytes.Equal(tnm.instance.GetFullNdf().Get().UDB.ID, + identity.Bytes()) { + tnm.requestProcess = mp + } + + return nil +} + +func (tnm *testNetworkManager) AddService(clientID *id.ID, + newService message.Service, + response message.Processor) { + tnm.responseProcessor = response + return +} + +func (tnm *testNetworkManager) CheckInProgressMessages() { + return +} + +func (tnm *testNetworkManager) GetMaxMessageLength() int { + return 700 +} + +func (tnm *testNetworkManager) AddIdentity(id *id.ID, validUntil time.Time, persistent bool) { + return +} + +func (tnm *testNetworkManager) DeleteClientFingerprints(identity *id.ID) { + return +} + +func (tnm *testNetworkManager) Process(ecrMsg format.Message, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + +} + +func (tnm *testNetworkManager) String() string { + return "mockPRocessor" +} + +func (tnm *testNetworkManager) DeleteService(clientID *id.ID, toDelete message.Service, processor message.Processor) { + return +} + +func (tnm *testNetworkManager) IsHealthy() bool { + return true +} + +func (tnm *testNetworkManager) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) { + //TODO implement me + panic("implement me") +} + +func (tnm *testNetworkManager) GetAddressSpace() uint8 { + return 8 +} + +func (tnm *testNetworkManager) GetInstance() *network.Instance { + return tnm.instance } -type dummyEventMgr struct{} +type mockUserStore struct{} -func (d *dummyEventMgr) Report(int, string, string, string) {} -func (tnm *testNetworkManager) GetEventManager() event.Reporter { - return &dummyEventMgr{} +func (m mockUserStore) PortableUserInfo() user.Info { + //TODO implement me + panic("implement me") } -func (tnm *testNetworkManager) GetInstance() *network.Instance { return tnm.instance } -func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { return nil } -func (tnm *testNetworkManager) Follow(interfaces.ClientErrorReport) (stoppable.Stoppable, error) { - return nil, nil +func (m mockUserStore) GetUsername() (string, error) { + //TODO implement me + panic("implement me") } -func (tnm *testNetworkManager) CheckGarbledMessages() {} -func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 } -func (tnm *testNetworkManager) GetSender() *gateway.Sender { return nil } -func (tnm *testNetworkManager) GetAddressSize() uint8 { return 0 } -func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { - return nil, nil + +func (m mockUserStore) GetReceptionRegistrationValidationSignature() []byte { + //TODO implement me + panic("implement me") +} + +type mockComms struct { + udHost *connect.Host +} + +func (m mockComms) SendRegisterUser(host *connect.Host, message *pb.UDBUserRegistration) (*messages.Ack, error) { + //TODO implement me + panic("implement me") +} + +func (m mockComms) SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) { + //TODO implement me + panic("implement me") } -func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {} -func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter) {} + +func (m mockComms) SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) { + //TODO implement me + panic("implement me") +} + +func (m mockComms) SendRemoveFact(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) { + //TODO implement me + panic("implement me") +} + +func (m mockComms) SendRemoveUser(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) { + //TODO implement me + panic("implement me") +} + +func (m *mockComms) AddHost(hid *id.ID, address string, cert []byte, params connect.HostParams) (host *connect.Host, err error) { + h, err := connect.NewHost(hid, address, cert, params) + if err != nil { + return nil, err + } + + m.udHost = h + return h, nil +} + +func (m mockComms) GetHost(hostId *id.ID) (*connect.Host, bool) { + return m.udHost, true +} + +type mockE2e struct{} + +func (m mockE2e) GetReceptionID() *id.ID { + //TODO implement me + panic("implement me") +} + +func (m mockE2e) GetGroup() *cyclic.Group { + return getGroup() +} + +type mockResponse struct { + c []contact.Contact + err error +} + +type mockChannel chan mockResponse func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ UDB: ndf.UDB{ - ID: id.DummyUser.Bytes(), - Cert: "", - Address: "address", - DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 49, 44, 34, - 70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58, - 51, 49, 54, 49, 50, 55, 48, 53, 56, 49, 51, 52, 50, 49, 54, 54, - 57, 52, 55, 125}, + ID: id.DummyUser.Bytes(), + Cert: testCert, + Address: "address", + DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 53, 48, 49, 53, 53, 53, 52, 54, 53, 49, 48, 54, 49, 56, 57, 53, 54, 51, 48, 54, 52, 49, 51, 53, 49, 57, 56, 55, 57, 52, 57, 50, 48, 56, 49, 52, 57, 52, 50, 57, 51, 57, 53, 49, 50, 51, 54, 52, 56, 49, 57, 55, 48, 50, 50, 49, 48, 55, 55, 50, 52, 52, 48, 49, 54, 57, 52, 55, 52, 57, 53, 53, 56, 55, 54, 50, 57, 53, 57, 53, 48, 54, 55, 57, 55, 48, 53, 48, 48, 54, 54, 56, 49, 57, 50, 56, 48, 52, 48, 53, 51, 50, 48, 57, 55, 54, 56, 56, 53, 57, 54, 57, 56, 57, 49, 48, 54, 56, 54, 50, 52, 50, 52, 50, 56, 49, 48, 51, 51, 51, 54, 55, 53, 55, 54, 52, 51, 54, 55, 54, 56, 53, 56, 48, 55, 56, 49, 52, 55, 49, 52, 53, 49, 52, 52, 52, 52, 53, 51, 57, 57, 51, 57, 57, 53, 50, 52, 52, 53, 51, 56, 48, 49, 48, 54, 54, 55, 48, 52, 50, 49, 55, 54, 57, 53, 57, 57, 57, 51, 52, 48, 54, 54, 54, 49, 50, 48, 54, 56, 57, 51, 54, 57, 48, 52, 55, 55, 54, 50, 49, 49, 56, 56, 53, 51, 50, 57, 57, 50, 54, 53, 48, 52, 57, 51, 54, 55, 54, 48, 57, 56, 56, 49, 55, 52, 52, 57, 53, 57, 54, 53, 50, 55, 53, 52, 52, 52, 49, 57, 55, 49, 54, 50, 52, 52, 56, 50, 55, 55, 50, 49, 48, 53, 56, 56, 57, 54, 51, 53, 54, 54, 53, 53, 53, 53, 49, 56, 50, 53, 49, 49, 50, 57, 50, 48, 49, 56, 48, 48, 54, 49, 56, 57, 48, 55, 48, 51, 53, 51, 51, 56, 57, 52, 49, 50, 57, 49, 55, 50, 56, 55, 57, 57, 52, 55, 53, 51, 49, 55, 55, 48, 53, 55, 55, 49, 50, 51, 57, 49, 51, 55, 54, 48, 50, 49, 55, 50, 54, 54, 52, 56, 52, 48, 48, 54, 48, 52, 48, 53, 56, 56, 53, 54, 52, 56, 56, 49, 52, 52, 51, 57, 56, 51, 51, 57, 54, 55, 48, 49, 53, 55, 52, 53, 50, 56, 51, 49, 51, 48, 53, 52, 49, 49, 49, 49, 49, 56, 51, 53, 52, 52, 52, 52, 48, 53, 54, 57, 48, 54, 52, 56, 57, 52, 54, 53, 50, 56, 51, 53, 50, 48, 48, 50, 48, 48, 49, 50, 51, 51, 48, 48, 53, 48, 49, 50, 52, 56, 57, 48, 49, 51, 54, 55, 52, 57, 55, 50, 49, 48, 55, 53, 54, 49, 50, 52, 52, 57, 55, 48, 50, 56, 55, 55, 51, 51, 50, 53, 50, 48, 57, 52, 56, 57, 49, 49, 56, 49, 54, 57, 50, 55, 50, 51, 57, 51, 57, 54, 50, 56, 48, 54, 54, 49, 57, 55, 48, 50, 48, 57, 49, 51, 54, 50, 49, 50, 53, 50, 54, 50, 53, 53, 55, 57, 54, 51, 56, 49, 57, 48, 51, 49, 54, 54, 53, 51, 56, 56, 49, 48, 56, 48, 51, 57, 53, 49, 53, 53, 55, 49, 53, 57, 48, 57, 57, 55, 49, 56, 53, 55, 54, 48, 50, 54, 48, 49, 55, 57, 52, 55, 53, 51, 57, 49, 51, 53, 52, 49, 48, 50, 49, 55, 52, 51, 57, 48, 50, 56, 48, 50, 51, 53, 51, 54, 56, 49, 56, 50, 49, 55, 50, 57, 52, 51, 49, 56, 48, 56, 56, 50, 51, 53, 52, 56, 55, 49, 52, 55, 53, 50, 56, 48, 57, 55, 49, 53, 48, 48, 51, 50, 48, 57, 50, 50, 53, 50, 56, 51, 57, 55, 57, 49, 57, 50, 53, 56, 51, 55, 48, 51, 57, 54, 48, 50, 55, 54, 48, 54, 57, 55, 52, 53, 54, 52, 51, 56, 52, 53, 54, 48, 51, 57, 55, 55, 55, 49, 53, 57, 57, 49, 57, 52, 57, 56, 56, 54, 56, 50, 49, 49, 54, 56, 55, 56, 55, 51, 51, 57, 52, 53, 49, 52, 52, 55, 57, 53, 57, 49, 57, 52, 48, 51, 53, 49, 49, 49, 51, 48, 53, 54, 54, 50, 49, 56, 57, 52, 55, 50, 49, 54, 53, 57, 53, 50, 57, 50, 48, 51, 51, 52, 48, 56, 55, 54, 50, 49, 49, 49, 56, 53, 54, 57, 51, 57, 50, 53, 48, 53, 56, 56, 55, 56, 53, 54, 55, 51, 56, 55, 50, 53, 57, 56, 52, 54, 53, 49, 51, 50, 54, 51, 50, 48, 56, 56, 57, 52, 53, 57, 53, 56, 57, 57, 54, 52, 55, 55, 50, 57, 51, 51, 52, 55, 51, 48, 52, 56, 56, 50, 51, 50, 52, 53, 48, 51, 50, 56, 56, 50, 49, 55, 51, 51, 53, 54, 55, 51, 50, 51, 52, 56, 53, 52, 55, 48, 51, 56, 50, 51, 49, 53, 55, 52, 53, 53, 48, 55, 55, 56, 55, 48, 50, 51, 52, 50, 53, 52, 51, 48, 57, 56, 56, 54, 56, 54, 49, 57, 54, 48, 55, 55, 52, 57, 55, 56, 51, 48, 51, 57, 49, 55, 52, 49, 51, 49, 54, 57, 54, 50, 49, 52, 50, 55, 57, 55, 56, 56, 51, 49, 55, 51, 50, 54, 56, 49, 56, 53, 57, 48, 49, 49, 53, 48, 52, 53, 51, 51, 56, 52, 57, 57, 55, 54, 51, 55, 55, 48, 55, 49, 52, 50, 49, 54, 48, 49, 54, 52, 49, 57, 53, 56, 49, 54, 50, 55, 49, 52, 49, 52, 56, 49, 51, 52, 50, 53, 56, 55, 53, 57, 55, 52, 49, 57, 49, 55, 51, 55, 49, 51, 57, 54, 51, 49, 51, 49, 56, 53, 50, 49, 53, 52, 49, 51, 44, 34, 70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58, 49, 54, 56, 48, 49, 53, 52, 49, 53, 49, 49, 50, 51, 51, 48, 57, 56, 51, 54, 51, 125}, }, E2E: ndf.Group{ Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" +