Skip to content
Snippets Groups Projects
Commit 0a34c211 authored by Josh Brooks's avatar Josh Brooks
Browse files

Finish up restructure of ud/ (production, not tests)

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