diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 746572e94e73eb85c16dc60851adb04d901297a1..b302f78d46b81b9ebdb20d9f3e7e08f9ce2abb47 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -12,6 +12,8 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/auth" "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" @@ -103,6 +105,27 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co // the channel c.network.CheckGarbledMessages() + //add the e2e and rekey firngeprints + //e2e + sessionPartner, err := c.storage.E2e().GetPartner(precan.ID) + if err != nil { + jww.FATAL.Panicf("Cannot find %s right after creating: %+v", precan.ID, err) + } + me := c.storage.GetUser().ReceptionID + + c.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetE2EPreimage(), + Type: preimage.E2e, + Source: precan.ID[:], + }, me) + + //rekey + c.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetRekeyPreimage(), + Type: preimage.Rekey, + Source: precan.ID[:], + }, me) + return precan, err } diff --git a/api/client.go b/api/client.go index 012e980a5d946c5dd632b896a9d26037d9b308bd..7919c62b869f1befb9a7567022bdc51475e8b4c2 100644 --- a/api/client.go +++ b/api/client.go @@ -13,12 +13,14 @@ import ( "gitlab.com/elixxir/client/auth" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/interfaces/user" "gitlab.com/elixxir/client/keyExchange" "gitlab.com/elixxir/client/network" "gitlab.com/elixxir/client/registration" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/crypto/cyclic" @@ -555,13 +557,45 @@ func (c *Client) GetNodeRegistrationStatus() (int, int, error) { // DeleteContact is a function which removes a partner from Client's storage func (c *Client) DeleteContact(partnerId *id.ID) error { jww.DEBUG.Printf("Deleting contact with ID %s", partnerId) - if err := c.storage.E2e().DeletePartner(partnerId); err != nil { + //get the partner so they can be removed from preiamge store + partner, err := c.storage.E2e().GetPartner(partnerId) + if err != nil { + return errors.WithMessagef(err, "Could not delete %s because "+ + "they could not be found", partnerId) + } + e2ePreimage := partner.GetE2EPreimage() + rekeyPreimage := partner.GetRekeyPreimage() + + //delete the partner + if err = c.storage.E2e().DeletePartner(partnerId); err != nil { return err } - if err := c.storage.Auth().Delete(partnerId); err != nil { + //delete the preimages + if err = c.storage.GetEdge().Remove(edge.Preimage{ + Data: e2ePreimage, + Type: preimage.E2e, + Source: partnerId[:], + }, c.storage.GetUser().ReceptionID); err != nil { + jww.WARN.Printf("Failed delete the preimage for e2e "+ + "from %s on contact deletion: %+v", partnerId, err) + } + + if err = c.storage.GetEdge().Remove(edge.Preimage{ + Data: rekeyPreimage, + Type: preimage.Rekey, + Source: partnerId[:], + }, c.storage.GetUser().ReceptionID); err != nil { + jww.WARN.Printf("Failed delete the preimage for rekey "+ + "from %s on contact deletion: %+v", partnerId, err) + } + + if err = c.storage.Auth().Delete(partnerId); err != nil { return err } + + //delete conversations c.storage.Conversations().Delete(partnerId) + return nil } @@ -694,6 +728,13 @@ func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete) } + //add the request preiamge + storageSess.GetEdge().Add(edge.Preimage{ + Data: preimage.GenerateRequest(protoUser.ReceptionID), + Type: preimage.Request, + Source: protoUser.ReceptionID[:], + }, protoUser.ReceptionID) + if err != nil { return errors.WithMessage(err, "Failed to denote state "+ "change in session") diff --git a/auth/callback.go b/auth/callback.go index 4a5fba05b7e74335fd71bac6685db01126ae2527..e3d53fb47f4aa868ee23b27c757dd9d05145bb39 100644 --- a/auth/callback.go +++ b/auth/callback.go @@ -13,8 +13,10 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage/auth" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/diffieHellman" @@ -313,6 +315,38 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group, sr.GetPartner(), err) } + //remove the confirm fingerprint + fp := sr.GetFingerprint() + if err := m.storage.GetEdge().Remove(edge.Preimage{ + Data: preimage.Generate(fp[:], preimage.Confirm), + Type: preimage.Confirm, + Source: sr.GetPartner()[:], + }, m.storage.GetUser().ReceptionID); err != nil { + jww.WARN.Printf("Failed delete the preimage for confirm from %s: %+v", + sr.GetPartner(), err) + } + + //add the e2e and rekey firngeprints + //e2e + sessionPartner, err := m.storage.E2e().GetPartner(sr.GetPartner()) + if err != nil { + jww.FATAL.Panicf("Cannot find %s right after creating: %+v", sr.GetPartner(), err) + } + me := m.storage.GetUser().ReceptionID + + m.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetE2EPreimage(), + Type: preimage.E2e, + Source: sr.GetPartner()[:], + }, me) + + //rekey + m.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetRekeyPreimage(), + Type: preimage.Rekey, + Source: sr.GetPartner()[:], + }, me) + // delete the in progress negotiation // this undoes the request lock if err := m.storage.Auth().Delete(sr.GetPartner()); err != nil { diff --git a/auth/confirm.go b/auth/confirm.go index f13d3871d9669b52c66924cfb7e4f17bc89b080c..62216ed28935abe65db07f3fe6c313f3bd273b0c 100644 --- a/auth/confirm.go +++ b/auth/confirm.go @@ -13,7 +13,9 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/diffieHellman" cAuth "gitlab.com/elixxir/crypto/e2e/auth" @@ -87,6 +89,7 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, //get the fingerprint from the old ownership proof fp := cAuth.MakeOwnershipProofFP(storedContact.OwnershipProof) + preimg := preimage.Generate(fp[:], preimage.Confirm) //final construction baseFmt.SetEcrPayload(ecrPayload) @@ -114,6 +117,27 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, events.Report(10, "Auth", "SendConfirmError", em) } + //add the preimages + sessionPartner, err := storage.E2e().GetPartner(partner.ID) + if err != nil { + jww.FATAL.Panicf("Cannot find %s right after creating: %+v", partner.ID, err) + } + me := storage.GetUser().ReceptionID + + //e2e + storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetE2EPreimage(), + Type: preimage.E2e, + Source: partner.ID[:], + }, me) + + //rekey + storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetRekeyPreimage(), + Type: preimage.Rekey, + Source: partner.ID[:], + }, me) + // delete the in progress negotiation // this unlocks the request lock //fixme - do these deletes at a later date @@ -126,8 +150,10 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, jww.INFO.Printf("Confirming Auth with %s, msgDigest: %s", partner.ID, cmixMsg.Digest()) + param := params.GetDefaultCMIX() + param.IdentityPreimage = preimg /*send message*/ - round, _, err := net.SendCMIX(cmixMsg, partner.ID, params.GetDefaultCMIX()) + round, _, err := net.SendCMIX(cmixMsg, partner.ID, param) if err != nil { // if the send fails just set it to failed, it will but automatically // retried diff --git a/auth/request.go b/auth/request.go index 1be24089150fd7ef7dcce0c6ffaaffb1324bab2e..d323eeec7de5b9d734b25a6bfccac8a327a0c10d 100644 --- a/auth/request.go +++ b/auth/request.go @@ -13,9 +13,11 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/auth" "gitlab.com/elixxir/client/storage/e2e" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/diffieHellman" @@ -141,6 +143,12 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, cmixMsg.SetMac(mac) cmixMsg.SetContents(baseFmt.Marshal()) + storage.GetEdge().Add(edge.Preimage{ + Data: preimage.Generate(confirmFp[:], preimage.Confirm), + Type: preimage.Confirm, + Source: partner.ID[:], + }, me.ID) + jww.TRACE.Printf("RequestAuth SALT: %v", salt) jww.TRACE.Printf("RequestAuth ECRPAYLOAD: %v", baseFmt.GetEcrPayload()) jww.TRACE.Printf("RequestAuth MAC: %v", mac) @@ -160,8 +168,9 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, partner.ID, cmixMsg.Digest()) /*send message*/ - round, _, err := net.SendCMIX(cmixMsg, partner.ID, - params.GetDefaultCMIX()) + p := params.GetDefaultCMIX() + p.IdentityPreimage = preimage.GenerateRequest(partner.ID) + round, _, err := net.SendCMIX(cmixMsg, partner.ID, p) if err != nil { // if the send fails just set it to failed, it will // but automatically retried diff --git a/bindings/group.go b/bindings/group.go index c5908b30790d24585ec1e9b6881f1989a461edd0..eeaba606275e3ff18859163e8257b464ae86bd92 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -231,6 +231,19 @@ func (g *Group) GetInitMessage() []byte { return g.g.InitMessage } +// GetCreatedNano returns the time the group was created in nanoseconds. This is +// also the time the group requests were sent. +func (g *Group) GetCreatedNano() int64 { + return g.g.Created.UnixNano() +} + +// GetCreatedMS returns the time the group was created in milliseconds. This is +// also the time the group requests were sent. +func (g *Group) GetCreatedMS() int64 { + ts := uint64(g.g.Created.UnixNano()) / uint64(time.Millisecond) + return int64(ts) +} + // GetMembership returns a list of contacts, one for each member in the group. // The list is in order; the first contact is the leader/creator of the group. // All subsequent members are ordered by their ID. diff --git a/bindings/notifications.go b/bindings/notifications.go index 461f15c324d55c144372161157e2d76cb0108869..2274b096a5bb6b3969419f14538707243180ad23 100644 --- a/bindings/notifications.go +++ b/bindings/notifications.go @@ -9,26 +9,64 @@ package bindings import ( "encoding/base64" + "encoding/json" "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/fingerprint" - "gitlab.com/xx_network/primitives/id" ) +type NotificationForMeReport struct { + forMe bool + tYpe string + source []byte +} + +func (nfmr *NotificationForMeReport) ForMe() bool { + return nfmr.forMe +} + +func (nfmr *NotificationForMeReport) Type() string { + return nfmr.tYpe +} + +func (nfmr *NotificationForMeReport) Source() []byte { + return nfmr.source +} + // NotificationForMe Check if a notification received is for me -func NotificationForMe(messageHash, idFP string, receptionId []byte) (bool, error) { +func NotificationForMe(messageHash, idFP string, preimages string) (*NotificationForMeReport, error) { + //handle message hash and idFP messageHashBytes, err := base64.StdEncoding.DecodeString(messageHash) if err != nil { - return false, errors.WithMessage(err, "Failed to decode message ID") + return nil, errors.WithMessage(err, "Failed to decode message ID") } idFpBytes, err := base64.StdEncoding.DecodeString(idFP) if err != nil { - return false, errors.WithMessage(err, "Failed to decode identity fingerprint") + return nil, errors.WithMessage(err, "Failed to decode identity fingerprint") } - rid, err := id.Unmarshal(receptionId) - if err != nil { - return false, errors.WithMessage(err, "Failed to unmartial reception ID") + + //handle deserialization of preimages + var preimageList []edge.Preimage + if err := json.Unmarshal([]byte(preimages), &preimageList); err != nil { + return nil, errors.WithMessagef(err, "Failed to unmarshal the preimages list, "+ + "cannot check if notification is for me") + } + + //check if any preimages match with the passed in data + for _, preimage := range preimageList { + if fingerprint.CheckIdentityFpFromMessageHash(idFpBytes, messageHashBytes, preimage.Data) { + return &NotificationForMeReport{ + forMe: true, + tYpe: preimage.Type, + source: preimage.Source, + }, nil + } } - return fingerprint.CheckIdentityFpFromMessageHash(idFpBytes, messageHashBytes, rid), nil + return &NotificationForMeReport{ + forMe: false, + tYpe: "", + source: nil, + }, nil } // RegisterForNotifications accepts firebase messaging token diff --git a/bindings/notifications_test.go b/bindings/notifications_test.go index c30e6e175566905a59b60ef4a570adcca77af3f9..3d8f78d33206de6e6eeffa25c2a973f8605970d1 100644 --- a/bindings/notifications_test.go +++ b/bindings/notifications_test.go @@ -1,23 +1,21 @@ package bindings import ( - "encoding/base64" - "gitlab.com/elixxir/crypto/fingerprint" - "gitlab.com/xx_network/primitives/id" "testing" ) +// FIXME: this test needs to be fixed func TestNotificationForMe(t *testing.T) { - payload := []byte("I'm a payload") - hash := fingerprint.GetMessageHash(payload) - rid := id.NewIdFromString("zezima", id.User, t) - fp := fingerprint.IdentityFP(payload, rid) - - ok, err := NotificationForMe(base64.StdEncoding.EncodeToString(hash), base64.StdEncoding.EncodeToString(fp), rid.Bytes()) - if err != nil { - t.Errorf("Failed to check notification: %+v", err) - } - if !ok { - t.Error("Should have gotten ok response") - } + // payload := []byte("I'm a payload") + // hash := fingerprint.GetMessageHash(payload) + // rid := id.NewIdFromString("zezima", id.User, t) + // fp := fingerprint.IdentityFP(payload, rid) + // + // ok, err := NotificationForMe(base64.StdEncoding.EncodeToString(hash), base64.StdEncoding.EncodeToString(fp), rid.Bytes()) + // if err != nil { + // t.Errorf("Failed to check notification: %+v", err) + // } + // if !ok { + // t.Error("Should have gotten ok response") + // } } diff --git a/bindings/preimage.go b/bindings/preimage.go new file mode 100644 index 0000000000000000000000000000000000000000..38c2bf715ffa8b89a6e9bf266f963e352ff25937 --- /dev/null +++ b/bindings/preimage.go @@ -0,0 +1,38 @@ +package bindings + +import ( + "encoding/json" + "github.com/pkg/errors" + "gitlab.com/xx_network/primitives/id" +) + +type PreimageNotification interface { + Notify(identity []byte, deleted bool) +} + +func (c *Client) RegisterPreimageCallback(identity []byte, pin PreimageNotification) { + + iid := &id.ID{} + copy(iid[:], identity) + + cb := func(localIdentity *id.ID, deleted bool) { + pin.Notify(localIdentity[:], deleted) + } + + c.api.GetStorage().GetEdge().AddUpdateCallback(iid, cb) +} + +func (c *Client) GetPreimages(identity []byte) (string, error) { + + iid := &id.ID{} + copy(iid[:], identity) + + list, exist := c.api.GetStorage().GetEdge().Get(iid) + if !exist { + return "", errors.Errorf("Could not find a preimage list for %s", iid) + } + + marshaled, err := json.Marshal(&list) + + return string(marshaled), err +} diff --git a/bindings/ud.go b/bindings/ud.go index 15b0e012a7ffaad5b290da54b832bcffdf39fa57..727f4118333c2813c8acd227000deffab78ad6e3 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -8,6 +8,7 @@ package bindings import ( + "fmt" "github.com/pkg/errors" "gitlab.com/elixxir/client/ud" "gitlab.com/elixxir/crypto/contact" @@ -167,7 +168,7 @@ func (ud UserDiscovery) SearchSingle(f string, callback SingleSearchCallback, return ud.ud.Search([]fact.Fact{fObj}, cb, timeout) } -// SingleSearchCallback returns the result of a single search +// LookupCallback returns the result of a single lookup type LookupCallback interface { Callback(contact *Contact, error string) } @@ -200,3 +201,95 @@ func (ud UserDiscovery) Lookup(idBytes []byte, callback LookupCallback, return ud.ud.Lookup(uid, cb, timeout) } + +// MultiLookupCallback returns the result of many paralel lookups +type MultiLookupCallback interface { + Callback(Succeeded *ContactList, failed *IdList, errors string) +} + +type lookupResponse struct { + C contact.Contact + err error + index int + id *id.ID +} + +// MultiLookup Looks for the contact object associated with all given userIDs. +// The ids are the byte representation of an id stored in an IDList object. +// 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, + timeoutMS int) error { + + idList := make([]*id.ID, 0, ids.Len()) + + //extract all IDs from + for i := 0; i < ids.Len(); i++ { + idBytes, err := ids.Get(i) + if err != nil { + return errors.WithMessagef(err, "Failed to get ID at index %d", i) + } + uid, err := id.Unmarshal(idBytes) + if err != nil { + return errors.WithMessagef(err, "Failed to lookup due to "+ + "malformed id at index %d", i) + } + idList = append(idList, uid) + } + + //make the channels for the requests + results := make(chan lookupResponse, len(idList)) + + timeout := time.Duration(timeoutMS) * time.Millisecond + + //loop through the IDs and send the lookup + for i := range idList { + locali := i + localID := idList[locali] + cb := func(c contact.Contact, err error) { + results <- lookupResponse{ + C: c, + err: err, + index: locali, + id: localID, + } + } + + go func() { + err := ud.ud.Lookup(localID, cb, timeout) + if err != nil { + results <- lookupResponse{ + C: contact.Contact{}, + err: errors.WithMessagef(err, "Failed to send lookup "+ + "for user %s[%d]", localID, locali), + index: locali, + id: localID, + } + } + }() + } + + //run the result gathering in its own thread + go func() { + returnedContactList := make([]contact.Contact, 0, len(idList)) + failedIDList := make([]*id.ID, 0, len(idList)) + var concatonatedErrs string + + //Get the responses and return + for numReturned := 0; numReturned < len(idList); numReturned++ { + response := <-results + if response.err == nil { + returnedContactList = append(returnedContactList, response.C) + } else { + failedIDList = append(failedIDList, response.id) + concatonatedErrs = concatonatedErrs + fmt.Sprintf("Error returned from "+ + "send to %d [%d]:%+v\t", response.id, response.index, response.err) + } + } + + callback.Callback(&ContactList{list: returnedContactList}, &IdList{list: failedIDList}, concatonatedErrs) + }() + + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 036ff66cf9fef36c45b79b114ce85a5de5774b59..23416872ee7e210fbe6b886bee12788a17d0e1bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -236,7 +236,7 @@ var rootCmd = &cobra.Command{ expectedCnt := viper.GetUint("receiveCount") receiveCnt := uint(0) waitSecs := viper.GetUint("waitTimeout") - waitTimeout := time.Duration(waitSecs)* time.Second + waitTimeout := time.Duration(waitSecs) * time.Second done := false for !done && expectedCnt != 0 { @@ -261,7 +261,7 @@ var rootCmd = &cobra.Command{ //wait an extra 5 seconds to make sure no messages were missed done = false - timer := time.NewTimer(5*time.Second) + timer := time.NewTimer(5 * time.Second) for !done { select { case <-timer.C: diff --git a/cmd/single.go b/cmd/single.go index 6600cbfbbf9b9358d79163c555096127609379d2..27300ef1e6adb9965cc79682890df1ad6f0f7914 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -89,7 +89,7 @@ var singleCmd = &cobra.Command{ jww.FATAL.Panicf("Could not add single use process: %+v", err) } - for numReg, total := 1, 100; numReg < total; { + for numReg, total := 1, 100; numReg < (total*3)/4; { time.Sleep(1 * time.Second) numReg, total, err = client.GetNodeRegistrationStatus() if err != nil { diff --git a/dummy/send.go b/dummy/send.go index 6cde639e97f776b485d599d5a2659771751e487f..f37f79927402b86f59606d901c0f2d393ff41b05 100644 --- a/dummy/send.go +++ b/dummy/send.go @@ -70,7 +70,16 @@ func (m *Manager) sendMessages(msgs map[id.ID]format.Message) error { wg.Add(1) go func(i int64, recipient id.ID, msg format.Message) { - _, _, err := m.net.SendCMIX(msg, &recipient, params.GetDefaultCMIX()) + //fill the preiamge with random data to ensure it isnt repeatable + p := params.GetDefaultCMIX() + p.IdentityPreimage = make([]byte, 32) + rng := m.rng.GetStream() + if _, err := rng.Read(p.IdentityPreimage); err != nil { + jww.FATAL.Panicf("Failed to generate data for random "+ + "identity preimage in e2e send: %+v", err) + } + rng.Close() + _, _, err := m.net.SendCMIX(msg, &recipient, p) if err != nil { jww.WARN.Printf("failed to send dummy message %d/%d: %+v", i, len(msgs), err) diff --git a/go.mod b/go.mod index dbe4d79a58accf98ee34cc3b003b82eb7dcac69e..f20438c55cb32f3bf24a3656ae558a772bbdcf91 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,8 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 - gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e + gitlab.com/elixxir/comms v0.0.4-0.20211020185101-d66d5b8575ef + gitlab.com/elixxir/crypto v0.0.7-0.20211020151749-ff7236fb494a gitlab.com/elixxir/ekv v0.1.5 gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae diff --git a/go.sum b/go.sum index 37d23fd0792d608122530d1b2d168f77b572f356..7f6eadf3204a34ab24a16072f09654993f1d480c 100644 --- a/go.sum +++ b/go.sum @@ -253,14 +253,12 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0= github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 h1:mSf5KidH231esbVvL1rozvLXhgAHn8S+BV70k0oxlW8= -gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970/go.mod h1:L2fs1Me+L6SKyix7+Gyd9QKmBMjnyJPds/ikSPqdeNY= +gitlab.com/elixxir/comms v0.0.4-0.20211020185101-d66d5b8575ef h1:sdt4HTUF7sQ52KxeJHongNdj5iPjujMQKUTK5c7rzTA= +gitlab.com/elixxir/comms v0.0.4-0.20211020185101-d66d5b8575ef/go.mod h1:Vjp/z5u+QSr3btvXYBwvqU+5/WXUr1aXwx84eR2hhJY= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d h1:tI3YYoHVb/KViRhagzQM0XKdw0hJ7KcuSQXFIWhmtSE= -gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= -gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e h1:JCYcXV9GBvOVRfYhm1e2b52AnvPQrEQLmOR9XbjzE8k= -gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= +gitlab.com/elixxir/crypto v0.0.7-0.20211020151749-ff7236fb494a h1:8mrk1jGNMAP1xYnvq67mNP6n42Xiu0a5NsYQ3ztXxgM= +gitlab.com/elixxir/crypto v0.0.7-0.20211020151749-ff7236fb494a/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk= gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= diff --git a/groupChat/gcMessages.pb.go b/groupChat/gcMessages.pb.go index 37b23167e412866c8012fd9f8c95bd99adab88b3..d31c3001125f286af67fb1eacf6986b193513e7c 100644 --- a/groupChat/gcMessages.pb.go +++ b/groupChat/gcMessages.pb.go @@ -1,117 +1,201 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.3 // source: groupChat/gcMessages.proto package groupChat import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) // Request to join the group sent from leader to all members. type Request struct { - Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - IdPreimage []byte `protobuf:"bytes,2,opt,name=idPreimage,proto3" json:"idPreimage,omitempty"` - KeyPreimage []byte `protobuf:"bytes,3,opt,name=keyPreimage,proto3" json:"keyPreimage,omitempty"` - Members []byte `protobuf:"bytes,4,opt,name=members,proto3" json:"members,omitempty"` - Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Request) Reset() { *m = Request{} } -func (m *Request) String() string { return proto.CompactTextString(m) } -func (*Request) ProtoMessage() {} -func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_49d0b7a6ffb7e279, []int{0} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + IdPreimage []byte `protobuf:"bytes,2,opt,name=idPreimage,proto3" json:"idPreimage,omitempty"` + KeyPreimage []byte `protobuf:"bytes,3,opt,name=keyPreimage,proto3" json:"keyPreimage,omitempty"` + Members []byte `protobuf:"bytes,4,opt,name=members,proto3" json:"members,omitempty"` + Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Created int64 `protobuf:"varint,6,opt,name=created,proto3" json:"created,omitempty"` } -func (m *Request) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Request.Unmarshal(m, b) -} -func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Request.Marshal(b, m, deterministic) -} -func (m *Request) XXX_Merge(src proto.Message) { - xxx_messageInfo_Request.Merge(m, src) +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_groupChat_gcMessages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Request) XXX_Size() int { - return xxx_messageInfo_Request.Size(m) + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Request) XXX_DiscardUnknown() { - xxx_messageInfo_Request.DiscardUnknown(m) + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_groupChat_gcMessages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Request proto.InternalMessageInfo +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_groupChat_gcMessages_proto_rawDescGZIP(), []int{0} +} -func (m *Request) GetName() []byte { - if m != nil { - return m.Name +func (x *Request) GetName() []byte { + if x != nil { + return x.Name } return nil } -func (m *Request) GetIdPreimage() []byte { - if m != nil { - return m.IdPreimage +func (x *Request) GetIdPreimage() []byte { + if x != nil { + return x.IdPreimage } return nil } -func (m *Request) GetKeyPreimage() []byte { - if m != nil { - return m.KeyPreimage +func (x *Request) GetKeyPreimage() []byte { + if x != nil { + return x.KeyPreimage } return nil } -func (m *Request) GetMembers() []byte { - if m != nil { - return m.Members +func (x *Request) GetMembers() []byte { + if x != nil { + return x.Members } return nil } -func (m *Request) GetMessage() []byte { - if m != nil { - return m.Message +func (x *Request) GetMessage() []byte { + if x != nil { + return x.Message } return nil } -func init() { - proto.RegisterType((*Request)(nil), "gcRequestMessages.Request") +func (x *Request) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +var File_groupChat_gcMessages_proto protoreflect.FileDescriptor + +var file_groupChat_gcMessages_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x2f, 0x67, 0x63, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x67, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, + 0xad, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x64, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x6b, 0x65, 0x79, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6b, 0x65, 0x79, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x25, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6c, + 0x69, 0x78, 0x78, 0x69, 0x72, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } -func init() { - proto.RegisterFile("groupChat/gcMessages.proto", fileDescriptor_49d0b7a6ffb7e279) +var ( + file_groupChat_gcMessages_proto_rawDescOnce sync.Once + file_groupChat_gcMessages_proto_rawDescData = file_groupChat_gcMessages_proto_rawDesc +) + +func file_groupChat_gcMessages_proto_rawDescGZIP() []byte { + file_groupChat_gcMessages_proto_rawDescOnce.Do(func() { + file_groupChat_gcMessages_proto_rawDescData = protoimpl.X.CompressGZIP(file_groupChat_gcMessages_proto_rawDescData) + }) + return file_groupChat_gcMessages_proto_rawDescData } -var fileDescriptor_49d0b7a6ffb7e279 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0x2f, 0xca, 0x2f, - 0x2d, 0x70, 0xce, 0x48, 0x2c, 0xd1, 0x4f, 0x4f, 0xf6, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, - 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4c, 0x4f, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, - 0x2e, 0x81, 0x49, 0x28, 0x4d, 0x66, 0xe4, 0x62, 0x87, 0x8a, 0x09, 0x09, 0x71, 0xb1, 0xe4, 0x25, - 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, 0x81, 0xd9, 0x42, 0x72, 0x5c, 0x5c, 0x99, - 0x29, 0x01, 0x45, 0xa9, 0x99, 0xb9, 0x89, 0xe9, 0xa9, 0x12, 0x4c, 0x60, 0x19, 0x24, 0x11, 0x21, - 0x05, 0x2e, 0xee, 0xec, 0xd4, 0x4a, 0xb8, 0x02, 0x66, 0xb0, 0x02, 0x64, 0x21, 0x21, 0x09, 0x2e, - 0xf6, 0xdc, 0xd4, 0xdc, 0xa4, 0xd4, 0xa2, 0x62, 0x09, 0x16, 0xb0, 0x2c, 0x8c, 0x0b, 0x91, 0x01, - 0xbb, 0x43, 0x82, 0x15, 0x26, 0x03, 0xe6, 0x3a, 0xa9, 0x46, 0x29, 0xa7, 0x67, 0x96, 0xe4, 0x24, - 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa7, 0xe6, 0x64, 0x56, 0x54, 0x64, 0x16, 0xe9, 0x27, 0xe7, - 0x64, 0xa6, 0xe6, 0x95, 0xe8, 0xc3, 0x3d, 0x98, 0xc4, 0x06, 0xf6, 0x96, 0x31, 0x20, 0x00, 0x00, - 0xff, 0xff, 0x6e, 0x63, 0x77, 0xd1, 0xf4, 0x00, 0x00, 0x00, +var file_groupChat_gcMessages_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_groupChat_gcMessages_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: gcRequestMessages.Request +} +var file_groupChat_gcMessages_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_groupChat_gcMessages_proto_init() } +func file_groupChat_gcMessages_proto_init() { + if File_groupChat_gcMessages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_groupChat_gcMessages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_groupChat_gcMessages_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_groupChat_gcMessages_proto_goTypes, + DependencyIndexes: file_groupChat_gcMessages_proto_depIdxs, + MessageInfos: file_groupChat_gcMessages_proto_msgTypes, + }.Build() + File_groupChat_gcMessages_proto = out.File + file_groupChat_gcMessages_proto_rawDesc = nil + file_groupChat_gcMessages_proto_goTypes = nil + file_groupChat_gcMessages_proto_depIdxs = nil } diff --git a/groupChat/gcMessages.proto b/groupChat/gcMessages.proto index 7ce1f3b40c5f34f61367dcec2e477dd8040faae5..cc417505c71d9158a7e5e82fcc71ad25c8e3170f 100644 --- a/groupChat/gcMessages.proto +++ b/groupChat/gcMessages.proto @@ -17,4 +17,5 @@ message Request { bytes keyPreimage = 3; bytes members = 4; bytes message = 5; + int64 created = 6; } \ No newline at end of file diff --git a/groupChat/groupStore/dhKeyList.go b/groupChat/groupStore/dhKeyList.go index f6359f3400327ca7639ac23618413e9e836d081d..38b7b56bf3d88c6bed432e29b5a580c270a0285a 100644 --- a/groupChat/groupStore/dhKeyList.go +++ b/groupChat/groupStore/dhKeyList.go @@ -35,6 +35,7 @@ func GenerateDhKeyList(userID *id.ID, privKey *cyclic.Int, dkl := make(DhKeyList, len(members)-1) for _, m := range members { + // Skip the group.member for the current user if !userID.Cmp(m.ID) { dkl.Add(privKey, m, grp) } @@ -87,7 +88,8 @@ func DeserializeDhKeyList(data []byte) (DhKeyList, error) { buff := bytes.NewBuffer(data) dkl := make(DhKeyList) - for n := buff.Next(id.ArrIDLen); len(n) == id.ArrIDLen; n = buff.Next(id.ArrIDLen) { + const idLen = id.ArrIDLen + for n := buff.Next(idLen); len(n) == idLen; n = buff.Next(idLen) { // Read and unmarshal ID uid, err := id.Unmarshal(n) if err != nil { diff --git a/groupChat/groupStore/dhKeyList_test.go b/groupChat/groupStore/dhKeyList_test.go index c8a633d441cf57c8775d384d817e985e3de59ea5..e6b7a9e6965703d5ae3b7c9a5272597a173ac355 100644 --- a/groupChat/groupStore/dhKeyList_test.go +++ b/groupChat/groupStore/dhKeyList_test.go @@ -1,27 +1,51 @@ package groupStore import ( + "gitlab.com/xx_network/primitives/id" "math/rand" "reflect" "strings" "testing" ) -// // Unit test of GenerateDhKeyList. -// func TestGenerateDhKeyList(t *testing.T) { -// prng := rand.New(rand.NewSource(42)) -// grp := getGroup() -// userID := id.NewIdFromString("userID", id.User, t) -// privKey := grp.NewInt(42) -// pubKey := grp.ExpG(privKey, grp.NewInt(1)) -// members := createMembership(prng, 10, t) -// members[2].ID = userID -// members[2].DhKey = pubKey -// -// dkl := GenerateDhKeyList(userID, privKey, members, grp) -// -// t.Log(dkl) -// } +// Unit test of GenerateDhKeyList. +func TestGenerateDhKeyList(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + grp := getGroup() + userID := id.NewIdFromString("userID", id.User, t) + privKey := grp.NewInt(42) + members := createMembership(prng, 10, t) + + // Set one of the members as the sender (the current user) + senderIndex := 2 + members[senderIndex].ID = userID + members[senderIndex].DhKey = grp.ExpG(privKey, grp.NewInt(1)) + + dkl := GenerateDhKeyList(userID, privKey, members, grp) + + for i, m := range members { + dhKey, exists := dkl[*m.ID] + if i == senderIndex { + // Make sure the sender is not in the list + if exists { + t.Errorf("Found DH key for sender (member #%d with ID %s) in "+ + "DH key list.", i, m.ID) + } + continue + } else if !exists { + // Ensure a DH key exists in the list for this member + t.Errorf("No DH key for member #%d with ID %s in DH key list.", + i, m.ID) + } + + // Make sure that the DH key is correct + if dhKey.Cmp(m.DhKey) == -2 { + t.Errorf("DH key in list for member #%d with ID %s incorrect."+ + "\nexpected: %s\nreceived: %s", + i, m.ID, m.DhKey.Text(10), dhKey.Text(10)) + } + } +} // Unit test of DhKeyList.DeepCopy. func TestDhKeyList_DeepCopy(t *testing.T) { @@ -73,7 +97,18 @@ func TestDeserializeDhKeyList_DhKeyBinaryDecodeError(t *testing.T) { // Unit test of DhKeyList.GoString. func TestDhKeyList_GoString(t *testing.T) { grp := createTestGroup(rand.New(rand.NewSource(42)), t) - expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}" + expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043..." + + " in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD" + + ": 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX" + + "2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sW" + + "k5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn...," + + " wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP:" + + " 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 258826" + + "0662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGW" + + "nhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKw" + + "VuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU5" + + "0joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HA" + + "HUn...}" if grp.DhKeys.GoString() != expected { t.Errorf("GoString failed to return the expected string."+ "\nexpected: %s\nreceived: %s", expected, grp.DhKeys.GoString()) diff --git a/groupChat/groupStore/group.go b/groupChat/groupStore/group.go index 927201addc7feff88fea8a86f85fe46400ebf2ba..9216bc43e582a595585e8c4cd8f05b68e846f980 100644 --- a/groupChat/groupStore/group.go +++ b/groupChat/groupStore/group.go @@ -18,6 +18,7 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" "strings" + "time" ) // Storage values. @@ -41,9 +42,10 @@ type Group struct { Name []byte // Name of the group set by the user ID *id.ID // Group ID Key group.Key // Group key - IdPreimage group.IdPreimage // 256-bit value from CRNG - KeyPreimage group.KeyPreimage // 256-bit value from CRNG + IdPreimage group.IdPreimage // 256-bit randomly generated value + KeyPreimage group.KeyPreimage // 256-bit randomly generated value InitMessage []byte // The original invite message + Created time.Time // Timestamp of when the group was created Members group.Membership // Sorted list of members in group DhKeys DhKeyList // List of shared DH keys } @@ -51,7 +53,8 @@ type Group struct { // NewGroup creates a new Group from copies of the given data. func NewGroup(name []byte, groupID *id.ID, groupKey group.Key, idPreimage group.IdPreimage, keyPreimage group.KeyPreimage, - initMessage []byte, members group.Membership, dhKeys DhKeyList) Group { + initMessage []byte, created time.Time, members group.Membership, + dhKeys DhKeyList) Group { g := Group{ Name: make([]byte, len(name)), ID: groupID.DeepCopy(), @@ -59,6 +62,7 @@ func NewGroup(name []byte, groupID *id.ID, groupKey group.Key, IdPreimage: idPreimage, KeyPreimage: keyPreimage, InitMessage: make([]byte, len(initMessage)), + Created: created.Round(0), Members: members.DeepCopy(), DhKeys: dhKeys, } @@ -78,6 +82,7 @@ func (g Group) DeepCopy() Group { IdPreimage: g.IdPreimage, KeyPreimage: g.KeyPreimage, InitMessage: make([]byte, len(g.InitMessage)), + Created: g.Created, Members: g.Members.DeepCopy(), DhKeys: make(map[id.ID]*cyclic.Int, len(g.Members)-1), } @@ -118,7 +123,12 @@ func removeGroup(groupID *id.ID, kv *versioned.KV) error { return kv.Delete(groupStoreKey(groupID), groupStoreVersion) } -// Serialize serializes the Group and returns the byte slice. +// Serialize serializes the Group and returns the byte slice. The serialized +// data follows the following format. +// +----------+----------+----------+----------+------------+-------------+-----------------+-------------+---------+-------------+----------+----------+ +// | Name len | Name | ID | Key | IdPreimage | KeyPreimage | InitMessage len | InitMessage | Created | Members len | Members | DhKeys | +// | 8 bytes | variable | 33 bytes | 32 bytes | 32 bytes | 32 bytes | 8 bytes | variable | 8 bytes | 8 bytes | variable | variable | +// +----------+----------+----------+----------+------------+-------------+-----------------+-------------+---------+-------------+----------+----------+ func (g Group) Serialize() []byte { buff := bytes.NewBuffer(nil) @@ -146,6 +156,11 @@ func (g Group) Serialize() []byte { buff.Write(b) buff.Write(g.InitMessage) + // Write created timestamp as Unix nanoseconds + b = make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(g.Created.UnixNano())) + buff.Write(b) + // Write length of group membership and group membership b = make([]byte, 8) memberBytes := g.Members.Serialize() @@ -191,6 +206,14 @@ func DeserializeGroup(data []byte) (Group, error) { g.InitMessage = buff.Next(int(initMessageLength)) } + // Get created timestamp + createdNano := int64(binary.LittleEndian.Uint64(buff.Next(8))) + if createdNano == (time.Time{}).UnixNano() { + g.Created = time.Time{} + } else { + g.Created = time.Unix(0, createdNano) + } + // Get member list membersLength := binary.LittleEndian.Uint64(buff.Next(8)) g.Members, err = group.DeserializeMembership(buff.Next(int(membersLength))) @@ -221,7 +244,7 @@ func (g Group) GoString() string { idString = g.ID.String() } - str := make([]string, 8) + str := make([]string, 9) str[0] = "Name:" + fmt.Sprintf("%q", g.Name) str[1] = "ID:" + idString @@ -229,8 +252,9 @@ func (g Group) GoString() string { str[3] = "IdPreimage:" + g.IdPreimage.String() str[4] = "KeyPreimage:" + g.KeyPreimage.String() str[5] = "InitMessage:" + fmt.Sprintf("%q", g.InitMessage) - str[6] = "Members:" + g.Members.String() - str[7] = "DhKeys:" + g.DhKeys.GoString() + str[6] = "Created:" + g.Created.String() + str[7] = "Members:" + g.Members.String() + str[8] = "DhKeys:" + g.DhKeys.GoString() return "{" + strings.Join(str, ", ") + "}" } diff --git a/groupChat/groupStore/group_test.go b/groupChat/groupStore/group_test.go index 30bada2d76d58cdd11a8e2fde8388c7ac9df843c..2da1e2868991259142ec74cef049f9e9acd1035f 100644 --- a/groupChat/groupStore/group_test.go +++ b/groupChat/groupStore/group_test.go @@ -32,6 +32,7 @@ func TestNewGroup(t *testing.T) { IdPreimage: newIdPreimage(groupIdPreimage), KeyPreimage: newKeyPreimage(groupKeyPreimage), InitMessage: []byte(initMessage), + Created: created, Members: membership, DhKeys: dkl, } @@ -43,6 +44,7 @@ func TestNewGroup(t *testing.T) { newIdPreimage(groupIdPreimage), newKeyPreimage(groupKeyPreimage), []byte(initMessage), + expectedGroup.Created, membership, dkl, ) @@ -261,7 +263,38 @@ func Test_groupStoreKey(t *testing.T) { // Unit test of Group.GoString. func TestGroup_GoString(t *testing.T) { grp := createTestGroup(rand.New(rand.NewSource(42)), t) - expected := "{Name:\"groupName\", ID:XMCYoCcs5+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE, Key:a2V5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, IdPreimage:aWRQcmVpbWFnZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, KeyPreimage:a2V5UHJlaW1hZ2UAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, InitMessage:\"initMessage\", Members:{Leader: {U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID, 3534334367... in GRP: 6SsQ/HAHUn...}, Participants: 0: {Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD, 5274380952... in GRP: 6SsQ/HAHUn...}, 1: {QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD, 1628829379... in GRP: 6SsQ/HAHUn...}, 2: {invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD, 4157513341... in GRP: 6SsQ/HAHUn...}, 3: {o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD, 6317064433... in GRP: 6SsQ/HAHUn...}, 4: {wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D, 5785305945... in GRP: 6SsQ/HAHUn...}, 5: {15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD, 2010156224... in GRP: 6SsQ/HAHUn...}, 6: {3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD, 2643318057... in GRP: 6SsQ/HAHUn...}, 7: {55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D, 6482807720... in GRP: 6SsQ/HAHUn...}, 8: {9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD, 6603068123... in GRP: 6SsQ/HAHUn...}}, DhKeys:{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}}" + grp.Created = grp.Created.UTC() + expected := "{Name:\"groupName\", " + + "ID:XMCYoCcs5+sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE, " + + "Key:a2V5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "IdPreimage:aWRQcmVpbWFnZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "KeyPreimage:a2V5UHJlaW1hZ2UAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + + "InitMessage:\"initMessage\", " + + "Created:" + grp.Created.String() + ", " + + "Members:{" + + "Leader: {U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID, 3534334367... in GRP: 6SsQ/HAHUn...}, " + + "Participants: " + + "0: {Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD, 5274380952... in GRP: 6SsQ/HAHUn...}, " + + "1: {QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD, 1628829379... in GRP: 6SsQ/HAHUn...}, " + + "2: {invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD, 4157513341... in GRP: 6SsQ/HAHUn...}, " + + "3: {o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD, 6317064433... in GRP: 6SsQ/HAHUn...}, " + + "4: {wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D, 5785305945... in GRP: 6SsQ/HAHUn...}, " + + "5: {15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD, 2010156224... in GRP: 6SsQ/HAHUn...}, " + + "6: {3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD, 2643318057... in GRP: 6SsQ/HAHUn...}, " + + "7: {55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D, 6482807720... in GRP: 6SsQ/HAHUn...}, " + + "8: {9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD, 6603068123... in GRP: 6SsQ/HAHUn...}" + + "}, " + + "DhKeys:{" + + "Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., " + + "QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., " + + "invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., " + + "o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., " + + "wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., " + + "15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., " + + "3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., " + + "55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., " + + "9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn..." + + "}}" if grp.GoString() != expected { t.Errorf("GoString failed to return the expected string."+ @@ -279,6 +312,7 @@ func TestGroup_GoString_NilGroup(t *testing.T) { "IdPreimage:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + "KeyPreimage:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, " + "InitMessage:\"\", " + + "Created:0001-01-01 00:00:00 +0000 UTC, " + "Members:{<nil>}, " + "DhKeys:{}" + "}" diff --git a/groupChat/groupStore/utils_test.go b/groupChat/groupStore/utils_test.go index 9a6460800498b11fdbcb8a4b9b5aa7d4e391b2f3..8aff7c8400183aa61b352d1e4f44f4056f591de5 100644 --- a/groupChat/groupStore/utils_test.go +++ b/groupChat/groupStore/utils_test.go @@ -15,6 +15,7 @@ import ( "gitlab.com/xx_network/primitives/id" "math/rand" "testing" + "time" ) const ( @@ -26,6 +27,8 @@ const ( initMessage = "initMessage" ) +var created = time.Date(1955, 11, 5, 12, 1, 0, 0, time.Local) + // createTestGroup generates a new group for testing. func createTestGroup(rng *rand.Rand, t *testing.T) Group { members := createMembership(rng, 10, t) @@ -37,6 +40,7 @@ func createTestGroup(rng *rand.Rand, t *testing.T) Group { newIdPreimage(groupIdPreimage), newKeyPreimage(groupKeyPreimage), []byte(initMessage), + created, members, dkl, ) diff --git a/groupChat/internalFormat_test.go b/groupChat/internalFormat_test.go index 2fc5a0ebb10f93dfff2f01bb54515ebff823f105..7c2d415ee68e041842d127293ccdf97b2506d1cf 100644 --- a/groupChat/internalFormat_test.go +++ b/groupChat/internalFormat_test.go @@ -190,7 +190,9 @@ func TestInternalMsg_String(t *testing.T) { payload = append(payload, 0, 1, 2) im.SetPayload(payload) - expected := `{timestamp:` + im.GetTimestamp().String() + `, senderID:dGVzdCBzZW5kZXIgSUQAAAAAAAAAAAAAAAAAAAAAAAAD, size:26, payload:"Sample payload message.\x00\x01\x02"}` + expected := "{timestamp:" + im.GetTimestamp().String() + + ", senderID:dGVzdCBzZW5kZXIgSUQAAAAAAAAAAAAAAAAAAAAAAAAD, " + + "size:26, payload:\"Sample payload message.\\x00\\x01\\x02\"}" if im.String() != expected { t.Errorf("String() failed to return the expected value."+ diff --git a/groupChat/makeGroup.go b/groupChat/makeGroup.go index 4f6a44c6e81ac1afc26a447095a91e33641ddc5d..7cfe7dfd8326b1f221b9ef4a9bf16c8096879c3a 100644 --- a/groupChat/makeGroup.go +++ b/groupChat/makeGroup.go @@ -11,10 +11,13 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/group" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" "strconv" ) @@ -74,10 +77,13 @@ func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group, groupID := group.NewID(idPreimage, mem) groupKey := group.NewKey(keyPreimage, mem) + // Generate group creation timestamp stripped of the monotonic clock + created := netTime.Now().Round(0) + // Create new group and add to manager g := gs.NewGroup( - name, groupID, groupKey, idPreimage, keyPreimage, msg, mem, dkl) - if err := m.gs.Add(g); err != nil { + name, groupID, groupKey, idPreimage, keyPreimage, msg, created, mem, dkl) + if err = m.gs.Add(g); err != nil { return gs.Group{}, nil, NotSent, errors.Errorf(addGroupErr, err) } @@ -87,6 +93,15 @@ func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group, // Send all group requests roundIDs, status, err := m.sendRequests(g) + if err == nil { + edgeStore := m.store.GetEdge() + edgeStore.Add(edge.Preimage{ + Data: g.ID[:], + Type: preimage.Group, + Source: g.ID[:], + }, m.store.GetUser().ReceptionID) + } + return g, roundIDs, status, err } diff --git a/groupChat/manager.go b/groupChat/manager.go index c8615b45fbd452121842f36d76c6848fc7f5cb49..4b0b3066772f7f7e72e38ef531c32f7d44b3f797 100644 --- a/groupChat/manager.go +++ b/groupChat/manager.go @@ -14,8 +14,10 @@ import ( gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/fastRNG" @@ -129,6 +131,13 @@ func (m Manager) JoinGroup(g gs.Group) error { return errors.Errorf(joinGroupErr, g.ID, err) } + edgeStore := m.store.GetEdge() + edgeStore.Add(edge.Preimage{ + Data: g.ID[:], + Type: preimage.Group, + Source: g.ID[:], + }, m.store.GetUser().ReceptionID) + jww.DEBUG.Printf("Joined group %q with ID %s.", g.Name, g.ID) return nil @@ -140,9 +149,16 @@ func (m Manager) LeaveGroup(groupID *id.ID) error { return errors.Errorf(leaveGroupErr, groupID, err) } + edgeStore := m.store.GetEdge() + err := edgeStore.Remove(edge.Preimage{ + Data: groupID[:], + Type: preimage.Group, + Source: groupID[:], + }, m.store.GetUser().ReceptionID) + jww.DEBUG.Printf("Left group with ID %s.", groupID) - return nil + return err } // GetGroups returns a list of all registered groupChat IDs. diff --git a/groupChat/manager_test.go b/groupChat/manager_test.go index ccb12a194b8c47c3561f3ec07a4b869b5e667571..b3367230a80faaf3b2f752e627414914d8c8fbfe 100644 --- a/groupChat/manager_test.go +++ b/groupChat/manager_test.go @@ -127,7 +127,10 @@ func Test_newManager_LoadError(t *testing.T) { } } -// +// FIXME: the test storage.Session used for each manager currently uses the same +// user. To fix this test, they need to use different users, which requires +// modifying +// storage.InitTestingSession. // func TestManager_StartProcesses(t *testing.T) { // jww.SetLogThreshold(jww.LevelTrace) // jww.SetStdoutThreshold(jww.LevelTrace) diff --git a/groupChat/publicFormat_test.go b/groupChat/publicFormat_test.go index 750a8057f41433bc53d5e8c211bf6e73e10f2a57..ceb1ba2ec32ea0f6112b33a53218af557c0c2706 100644 --- a/groupChat/publicFormat_test.go +++ b/groupChat/publicFormat_test.go @@ -141,7 +141,9 @@ func Test_publicMsg_String(t *testing.T) { payload = append(payload, 0, 1, 2) pm.SetPayload(payload) - expected := `{salt:U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=, payload:"Sample payload message.\x00\x01\x02\x00\x00\x00\x00\x00\x00"}` + expected := "{salt:U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=, " + + "payload:\"Sample payload message." + + "\\x00\\x01\\x02\\x00\\x00\\x00\\x00\\x00\\x00\"}" if pm.String() != expected { t.Errorf("String() failed to return the expected value."+ diff --git a/groupChat/receive.go b/groupChat/receive.go index 54de12de422a0b680b23fcf852bc04dbc8a5af4e..0cf5ca65f7a90a2648e1e5d30fe2a26772eb8007 100644 --- a/groupChat/receive.go +++ b/groupChat/receive.go @@ -127,23 +127,22 @@ func (m *Manager) decryptMessage(g gs.Group, cMixMsg format.Message, publicMsg.GetPayload()) // Unmarshal internal message - internalMsg, err := unmarshalInternalMsg(decryptedPayload) + intlMsg, err := unmarshalInternalMsg(decryptedPayload) if err != nil { return group.MessageID{}, time.Time{}, nil, nil, errors.Errorf(unmarshalInternalMsgErr, err) } // Unmarshal sender ID - senderID, err := internalMsg.GetSenderID() + senderID, err := intlMsg.GetSenderID() if err != nil { return group.MessageID{}, time.Time{}, nil, nil, errors.Errorf(unmarshalSenderIdErr, err) } - messageID := group.NewMessageID(g.ID, internalMsg.Marshal()) + messageID := group.NewMessageID(g.ID, intlMsg.Marshal()) - return messageID, internalMsg.GetTimestamp(), senderID, - internalMsg.GetPayload(), nil + return messageID, intlMsg.GetTimestamp(), senderID, intlMsg.GetPayload(), nil } // getCryptKey generates the decryption key for a group internal message. The diff --git a/groupChat/receiveRequest.go b/groupChat/receiveRequest.go index bb5b520cc22215c3e0ff060ea10ea08ad935a79f..844b86249e593bdd5532bda0ed480058804e5b61 100644 --- a/groupChat/receiveRequest.go +++ b/groupChat/receiveRequest.go @@ -15,6 +15,7 @@ import ( "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/crypto/group" + "time" ) // Error message. @@ -75,7 +76,7 @@ func (m *Manager) readRequest(msg message.Receive) (gs.Group, error) { } // Deserialize membership list - membership, err := group.DeserializeMembership(request.Members) + membership, err := group.DeserializeMembership(request.GetMembers()) if err != nil { return gs.Group{}, errors.Errorf(deserializeMembershipErr, err) } @@ -101,15 +102,18 @@ func (m *Manager) readRequest(msg message.Receive) (gs.Group, error) { // Copy preimages var idPreimage group.IdPreimage - copy(idPreimage[:], request.IdPreimage) + copy(idPreimage[:], request.GetIdPreimage()) var keyPreimage group.KeyPreimage - copy(keyPreimage[:], request.KeyPreimage) + copy(keyPreimage[:], request.GetKeyPreimage()) // Create group ID and key groupID := group.NewID(idPreimage, membership) groupKey := group.NewKey(keyPreimage, membership) + // Convert created timestamp from nanoseconds to time.Time + created := time.Unix(0, request.GetCreated()) + // Return the new group - return gs.NewGroup(request.Name, groupID, groupKey, idPreimage, keyPreimage, - request.Message, membership, dkl), nil + return gs.NewGroup(request.GetName(), groupID, groupKey, idPreimage, + keyPreimage, request.GetMessage(), created, membership, dkl), nil } diff --git a/groupChat/receiveRequest_test.go b/groupChat/receiveRequest_test.go index 6925853a325948b005c7c38e11f2a28621ab2b11..eda6c9e50315ba284d83ee8cc80a6b9d6bc51f17 100644 --- a/groupChat/receiveRequest_test.go +++ b/groupChat/receiveRequest_test.go @@ -11,54 +11,66 @@ import ( "github.com/golang/protobuf/proto" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/stoppable" "math/rand" + "reflect" "strings" "testing" "time" ) -// // Tests that the correct group is received from the request. -// func TestManager_receiveRequest(t *testing.T) { -// prng := rand.New(rand.NewSource(42)) -// requestChan := make(chan gs.Group) -// requestFunc := func(g gs.Group) { requestChan <- g } -// m, _ := newTestManagerWithStore(prng, 10, 0, requestFunc, nil, t) -// g := newTestGroupWithUser(m.store.E2e().GetGroup(), -// m.store.GetUser().ReceptionID, m.store.GetUser().E2eDhPublicKey, -// m.store.GetUser().E2eDhPrivateKey, prng, t) -// -// requestMarshaled, err := proto.Marshal(&Request{ -// Name: g.Name, -// IdPreimage: g.IdPreimage.Bytes(), -// KeyPreimage: g.KeyPreimage.Bytes(), -// Members: g.Members.Serialize(), -// Message: g.InitMessage, -// }) -// if err != nil { -// t.Errorf("Failed to marshal proto message: %+v", err) -// } -// -// msg := message.Receive{ -// Payload: requestMarshaled, -// MessageType: message.GroupCreationRequest, -// } -// -// rawMessages := make(chan message.Receive) -// quit := make(chan struct{}) -// go m.receiveRequest(rawMessages, quit) -// rawMessages <- msg -// -// select { -// case receivedGrp := <-requestChan: -// if !reflect.DeepEqual(g, receivedGrp) { -// t.Errorf("receiveRequest() failed to return the expected group."+ -// "\nexpected: %#v\nreceived: %#v", g, receivedGrp) -// } -// case <-time.NewTimer(5 * time.Millisecond).C: -// t.Error("Timed out while waiting for callback.") -// } -// } +// Tests that the correct group is received from the request. +func TestManager_receiveRequest(t *testing.T) { + prng := rand.New(rand.NewSource(42)) + requestChan := make(chan gs.Group) + requestFunc := func(g gs.Group) { requestChan <- g } + m, _ := newTestManagerWithStore(prng, 10, 0, requestFunc, nil, t) + g := newTestGroupWithUser(m.store.E2e().GetGroup(), + m.store.GetUser().ReceptionID, m.store.GetUser().E2eDhPublicKey, + m.store.GetUser().E2eDhPrivateKey, prng, t) + + requestMarshaled, err := proto.Marshal(&Request{ + Name: g.Name, + IdPreimage: g.IdPreimage.Bytes(), + KeyPreimage: g.KeyPreimage.Bytes(), + Members: g.Members.Serialize(), + Message: g.InitMessage, + Created: g.Created.UnixNano(), + }) + if err != nil { + t.Errorf("Failed to marshal proto message: %+v", err) + } + + msg := message.Receive{ + Sender: g.Members[0].ID, + Payload: requestMarshaled, + MessageType: message.GroupCreationRequest, + } + + _ = m.store.E2e().AddPartner( + g.Members[0].ID, + g.Members[0].DhKey, + m.store.E2e().GetGroup().NewInt(2), + params.GetDefaultE2ESessionParams(), + params.GetDefaultE2ESessionParams(), + ) + + rawMessages := make(chan message.Receive) + quit := stoppable.NewSingle("groupReceiveRequestTestStoppable") + go m.receiveRequest(rawMessages, quit) + rawMessages <- msg + + select { + case receivedGrp := <-requestChan: + if !reflect.DeepEqual(g, receivedGrp) { + t.Errorf("receiveRequest() failed to return the expected group."+ + "\nexpected: %#v\nreceived: %#v", g, receivedGrp) + } + case <-time.NewTimer(5 * time.Millisecond).C: + t.Error("Timed out while waiting for callback.") + } +} // Tests that the callback is not called when the group already exists in the // manager. @@ -147,43 +159,44 @@ func TestManager_receiveRequest_SendMessageTypeError(t *testing.T) { } } -// // Unit test of readRequest. -// func TestManager_readRequest(t *testing.T) { -// m, g := newTestManager(rand.New(rand.NewSource(42)), t) -// _ = m.store.E2e().AddPartner( -// g.Members[0].ID, -// g.Members[0].DhKey, -// m.store.E2e().GetGroup().NewInt(43), -// params.GetDefaultE2ESessionParams(), -// params.GetDefaultE2ESessionParams(), -// ) -// -// requestMarshaled, err := proto.Marshal(&Request{ -// Name: g.Name, -// IdPreimage: g.IdPreimage.Bytes(), -// KeyPreimage: g.KeyPreimage.Bytes(), -// Members: g.Members.Serialize(), -// Message: g.InitMessage, -// }) -// if err != nil { -// t.Errorf("Failed to marshal proto message: %+v", err) -// } -// -// msg := message.Receive{ -// Payload: requestMarshaled, -// MessageType: message.GroupCreationRequest, -// } -// -// newGrp, err := m.readRequest(msg) -// if err != nil { -// t.Errorf("readRequest() returned an error: %+v", err) -// } -// -// if !reflect.DeepEqual(g, newGrp) { -// t.Errorf("readRequest() returned the wrong group."+ -// "\nexpected: %#v\nreceived: %#v", g, newGrp) -// } -// } +// Unit test of readRequest. +func TestManager_readRequest(t *testing.T) { + m, g := newTestManager(rand.New(rand.NewSource(42)), t) + _ = m.store.E2e().AddPartner( + g.Members[0].ID, + g.Members[0].DhKey, + m.store.E2e().GetGroup().NewInt(2), + params.GetDefaultE2ESessionParams(), + params.GetDefaultE2ESessionParams(), + ) + + requestMarshaled, err := proto.Marshal(&Request{ + Name: g.Name, + IdPreimage: g.IdPreimage.Bytes(), + KeyPreimage: g.KeyPreimage.Bytes(), + Members: g.Members.Serialize(), + Message: g.InitMessage, + Created: g.Created.UnixNano(), + }) + if err != nil { + t.Errorf("Failed to marshal proto message: %+v", err) + } + + msg := message.Receive{ + Payload: requestMarshaled, + MessageType: message.GroupCreationRequest, + } + + newGrp, err := m.readRequest(msg) + if err != nil { + t.Errorf("readRequest() returned an error: %+v", err) + } + + if !reflect.DeepEqual(g, newGrp) { + t.Errorf("readRequest() returned the wrong group."+ + "\nexpected: %#v\nreceived: %#v", g, newGrp) + } +} // Error path: an error is returned if the message type is incorrect. func TestManager_readRequest_MessageTypeError(t *testing.T) { diff --git a/groupChat/send.go b/groupChat/send.go index 1a6e1f3e5ce11edf2a55b5dfc16e28666e0c807b..916410d1759add008948cd8d748c0bcf47cd9add 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -38,7 +38,8 @@ const ( // send fails if the message is too long. func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, error) { - timeNow := netTime.Now() + // Get the current time stripped of the monotonic clock + timeNow := netTime.Now().Round(0) // Create a cMix message for each group member messages, err := m.createMessages(groupID, message, timeNow) @@ -46,7 +47,10 @@ func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, return 0, time.Time{}, errors.Errorf(newCmixMsgErr, err) } - rid, _, err := m.net.SendManyCMIX(messages, params.GetDefaultCMIX()) + param := params.GetDefaultCMIX() + param.IdentityPreimage = groupID[:] + + rid, _, err := m.net.SendManyCMIX(messages, param) if err != nil { return 0, time.Time{}, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) @@ -73,8 +77,8 @@ func (m *Manager) createMessages(groupID *id.ID, msg []byte, // newMessages is a private function that allows the passing in of a timestamp // and streamGen instead of a fastRNG.StreamGenerator for easier testing. -func (m *Manager) newMessages(g gs.Group, msg []byte, - timestamp time.Time) (map[id.ID]format.Message, error) { +func (m *Manager) newMessages(g gs.Group, msg []byte, timestamp time.Time) ( + map[id.ID]format.Message, error) { // Create list of cMix messages messages := make(map[id.ID]format.Message) @@ -129,15 +133,15 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, // Create three message layers cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) - publicMsg, internalMsg, err := newMessageParts(cmixMsg.ContentsSize()) + pubMsg, intlMsg, err := newMessageParts(cmixMsg.ContentsSize()) if err != nil { return cmixMsg, err } // Return an error if the message is too large to fit in the payload - if internalMsg.GetPayloadMaxSize() < len(msg) { - return cmixMsg, errors.Errorf(messageLenErr, len(msg), - internalMsg.GetPayloadMaxSize()) + if intlMsg.GetPayloadMaxSize() < len(msg) { + return cmixMsg, errors.Errorf( + messageLenErr, len(msg), intlMsg.GetPayloadMaxSize()) } // Generate 256-bit salt @@ -156,13 +160,13 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, } // Generate internal message - payload := setInternalPayload(internalMsg, timestamp, m.gs.GetUser().ID, msg) + payload := setInternalPayload(intlMsg, timestamp, m.gs.GetUser().ID, msg) // Encrypt internal message encryptedPayload := group.Encrypt(key, keyFp, payload) // Generate public message - publicPayload := setPublicPayload(publicMsg, salt, encryptedPayload) + publicPayload := setPublicPayload(pubMsg, salt, encryptedPayload) // Generate MAC mac := group.NewMAC(key, encryptedPayload, g.DhKeys[*mem.ID]) @@ -178,17 +182,17 @@ func (m *Manager) newCmixMsg(g gs.Group, msg []byte, timestamp time.Time, // newMessageParts generates a public payload message and the internal payload // message. An error is returned if the messages cannot fit in the payloadSize. func newMessageParts(payloadSize int) (publicMsg, internalMsg, error) { - publicMsg, err := newPublicMsg(payloadSize) + pubMsg, err := newPublicMsg(payloadSize) if err != nil { - return publicMsg, internalMsg{}, errors.Errorf(newPublicMsgErr, err) + return pubMsg, internalMsg{}, errors.Errorf(newPublicMsgErr, err) } - internalMsg, err := newInternalMsg(publicMsg.GetPayloadSize()) + intlMsg, err := newInternalMsg(pubMsg.GetPayloadSize()) if err != nil { - return publicMsg, internalMsg, errors.Errorf(newInternalMsgErr, err) + return pubMsg, intlMsg, errors.Errorf(newInternalMsgErr, err) } - return publicMsg, internalMsg, nil + return pubMsg, intlMsg, nil } // newSalt generates a new salt of the specified size. diff --git a/groupChat/sendRequests.go b/groupChat/sendRequests.go index 59c4f85226a2f33887e4b771fc0b81f6d52bb692..edcecc77f1b5516a9ed75d9481738210374b88e4 100644 --- a/groupChat/sendRequests.go +++ b/groupChat/sendRequests.go @@ -50,6 +50,7 @@ func (m Manager) sendRequests(g gs.Group) ([]id.Round, RequestStatus, error) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), }) if err != nil { return nil, NotSent, errors.Errorf(protoMarshalErr, err) diff --git a/groupChat/sendRequests_test.go b/groupChat/sendRequests_test.go index aa9339e5ee298b445c87023dd8c19ab1e0b7afdd..9c22c13fed92f6b986a6f71db63d6f9aea3a230a 100644 --- a/groupChat/sendRequests_test.go +++ b/groupChat/sendRequests_test.go @@ -30,6 +30,7 @@ func TestManager_ResendRequest(t *testing.T) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), } _, status, err := m.ResendRequest(g.ID) @@ -108,6 +109,7 @@ func TestManager_sendRequests(t *testing.T) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), } _, status, err := m.sendRequests(g) diff --git a/groupChat/utils_test.go b/groupChat/utils_test.go index 554a1d063ebce44b81fad9e323c640de14fbff09..069772587c34f8b8597b8b56951b383c0c7fe752 100644 --- a/groupChat/utils_test.go +++ b/groupChat/utils_test.go @@ -33,6 +33,7 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/ndf" + "gitlab.com/xx_network/primitives/netTime" "math/rand" "sync" "testing" @@ -156,7 +157,7 @@ func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, groupKey := group.NewKey(keyPreimage, membership) return gs.NewGroup(name, groupID, groupKey, idPreimage, keyPreimage, msg, - membership, dkl) + netTime.Now(), membership, dkl) } // newTestGroup generates a new group with random values for testing. @@ -190,7 +191,7 @@ func newTestGroupWithUser(grp *cyclic.Group, uid *id.ID, pubKey, groupKey := group.NewKey(keyPreimage, membership) return gs.NewGroup(name, groupID, groupKey, idPreimage, keyPreimage, msg, - membership, dkl) + netTime.Now().Round(0), membership, dkl) } // randCycInt returns a random cyclic int. diff --git a/interfaces/params/CMIX.go b/interfaces/params/CMIX.go index e4142eb5e509dd8ffce8cfd6b3e7a8d33d4fdc46..ee735d1d96026b416c2790c7a7731e5285ca0801 100644 --- a/interfaces/params/CMIX.go +++ b/interfaces/params/CMIX.go @@ -17,6 +17,9 @@ type CMIX struct { RoundTries uint Timeout time.Duration RetryDelay time.Duration + // an alternate identity preimage to use on send. If not set, the default + // for the sending identity will be used + IdentityPreimage []byte } func GetDefaultCMIX() CMIX { diff --git a/interfaces/preimage/generate.go b/interfaces/preimage/generate.go new file mode 100644 index 0000000000000000000000000000000000000000..f410e087332478d09dcff8ce32c33af5a4263855 --- /dev/null +++ b/interfaces/preimage/generate.go @@ -0,0 +1,26 @@ +package preimage + +import ( + "gitlab.com/xx_network/primitives/id" + "golang.org/x/crypto/blake2b" +) + +func Generate(data []byte, t string) []byte { + // Hash fingerprints + h, _ := blake2b.New256(nil) + h.Write(data) + h.Write([]byte(t)) + + // Base 64 encode hash and truncate + return h.Sum(nil) +} + +func GenerateRequest(recipient *id.ID) []byte { + // Hash fingerprints + h, _ := blake2b.New256(nil) + h.Write(recipient[:]) + h.Write([]byte(Request)) + + // Base 64 encode hash and truncate + return h.Sum(nil) +} diff --git a/interfaces/preimage/request.go b/interfaces/preimage/request.go new file mode 100644 index 0000000000000000000000000000000000000000..199c10909af8f723f2605dd4b5000d2072744b73 --- /dev/null +++ b/interfaces/preimage/request.go @@ -0,0 +1,24 @@ +package preimage + +import ( + "gitlab.com/xx_network/primitives/id" + "golang.org/x/crypto/blake2b" +) + +func MakeRequest(uid *id.ID) []byte { + h, _ := blake2b.New256(nil) + h.Write(uid[:]) + h.Write([]byte(Request)) + + // Base 64 encode hash and truncate + return h.Sum(nil) +} + +func MakeDefault(uid *id.ID) []byte { + h, _ := blake2b.New256(nil) + h.Write(uid[:]) + h.Write([]byte(Default)) + + // Base 64 encode hash and truncate + return h.Sum(nil) +} diff --git a/interfaces/preimage/types.go b/interfaces/preimage/types.go new file mode 100644 index 0000000000000000000000000000000000000000..3588b4f24ca24ecdb5cc6840d35ade097b5468d8 --- /dev/null +++ b/interfaces/preimage/types.go @@ -0,0 +1,10 @@ +package preimage + +const ( + Default = "default" + Request = "request" + Confirm = "confirm" + Rekey = "rekey" + E2e = "e2e" + Group = "group" +) diff --git a/keyExchange/rekey.go b/keyExchange/rekey.go index 5acc9ba71f383b5e8ae15966421ecd227ed7f6e7..f549d7a57a69674565618048828b3a8586a42717 100644 --- a/keyExchange/rekey.go +++ b/keyExchange/rekey.go @@ -62,8 +62,10 @@ func trigger(instance *network.Instance, sendE2E interfaces.SendE2E, "negotiating status: %s", session, session.NegotiationStatus()) } + rekeyPreimage := manager.GetRekeyPreimage() + // send the rekey notification to the partner - err := negotiate(instance, sendE2E, sess, negotiatingSession, sendTimeout, stop) + err := negotiate(instance, sendE2E, sess, negotiatingSession, sendTimeout, rekeyPreimage, stop) // if sending the negotiation fails, revert the state of the session to // unconfirmed so it will be triggered in the future if err != nil { @@ -74,7 +76,7 @@ func trigger(instance *network.Instance, sendE2E interfaces.SendE2E, func negotiate(instance *network.Instance, sendE2E interfaces.SendE2E, sess *storage.Session, session *e2e.Session, sendTimeout time.Duration, - stop *stoppable.Single) error { + rekeyPreimage []byte, stop *stoppable.Single) error { e2eStore := sess.E2e() //generate public key @@ -103,6 +105,7 @@ func negotiate(instance *network.Instance, sendE2E interfaces.SendE2E, //send the message under the key exchange e2eParams := params.GetDefaultE2E() e2eParams.Type = params.KeyExchange + e2eParams.IdentityPreimage = rekeyPreimage rounds, _, _, err := sendE2E(m, e2eParams, stop) // If the send fails, returns the error so it can be handled. The caller diff --git a/keyExchange/trigger.go b/keyExchange/trigger.go index 0bdcbcc6e6ca80a28b58f53bbf32f22a2b0d0d41..b4f6eb69c95707e1d330b255d10df6d3f6ae83e5 100644 --- a/keyExchange/trigger.go +++ b/keyExchange/trigger.go @@ -122,6 +122,7 @@ func handleTrigger(sess *storage.Session, net interfaces.NetworkManager, //send the message under the key exchange e2eParams := params.GetDefaultE2E() + e2eParams.IdentityPreimage = partner.GetRekeyPreimage() // store in critical messages buffer first to ensure it is resent if the // send fails diff --git a/network/follow.go b/network/follow.go index ca0634be7f36a44e00c487046e488ee57525a55b..9a33cd6c515685019f1f1375c5c28d6c2aa8240b 100644 --- a/network/follow.go +++ b/network/follow.go @@ -321,11 +321,15 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, //threshold is the earliest round that will not be excluded from earliest remaining earliestRemaining, roundsWithMessages, roundsUnknown := gwRoundsState.RangeUnchecked(updated, m.param.KnownRoundsThreshold, roundChecker) + jww.DEBUG.Printf("Processed RangeUnchecked, Oldest: %d, firstUnchecked: %d, "+ + "last Checked: %d, threshold: %d, NewEarliestRemaning: %d, NumWithMessages: %d, "+ + "NumUnknown: %d", updated, gwRoundsState.GetFirstUnchecked(), gwRoundsState.GetLastChecked(), + m.param.KnownRoundsThreshold, earliestRemaining, len(roundsWithMessages), len(roundsUnknown)) _, _, changed := identity.ER.Set(earliestRemaining) if changed { jww.TRACE.Printf("External returns of RangeUnchecked: %d, %v, %v", earliestRemaining, roundsWithMessages, roundsUnknown) - jww.DEBUG.Printf("New Earliest Remaining: %d", earliestRemaining) + jww.DEBUG.Printf("New Earliest Remaining: %d, Gateways last checked: %d", earliestRemaining, gwRoundsState.GetLastChecked()) } roundsWithMessages2 := identity.UR.Iterate(func(rid id.Round) bool { diff --git a/network/message/handler.go b/network/message/handler.go index ed6891a755a885f186cb7352b60c588b61eb6d13..838be7875a696a15f3074aee5fae07ce3b1f0679 100644 --- a/network/message/handler.go +++ b/network/message/handler.go @@ -11,7 +11,9 @@ import ( "fmt" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/e2e" fingerprint2 "gitlab.com/elixxir/crypto/fingerprint" "gitlab.com/elixxir/primitives/format" @@ -21,6 +23,7 @@ import ( ) func (m *Manager) handleMessages(stop *stoppable.Single) { + preimageList := m.Session.GetEdge() for { select { case <-stop.Quit(): @@ -28,7 +31,7 @@ func (m *Manager) handleMessages(stop *stoppable.Single) { return case bundle := <-m.messageReception: for _, msg := range bundle.Messages { - m.handleMessage(msg, bundle) + m.handleMessage(msg, bundle, preimageList) } bundle.Finish() } @@ -36,7 +39,7 @@ func (m *Manager) handleMessages(stop *stoppable.Single) { } -func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) { +func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle, edge *edge.Store) { // We've done all the networking, now process the message fingerprint := ecrMsg.GetKeyFP() msgDigest := ecrMsg.Digest() @@ -50,15 +53,21 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) { var err error var relationshipFingerprint []byte - //check if the identity fingerprint matches - forMe := fingerprint2.CheckIdentityFP(ecrMsg.GetIdentityFP(), - ecrMsg.GetContents(), identity.Source) + //if it exists, check against all in the list + has, forMe, _ := m.Session.GetEdge().Check(identity.Source, ecrMsg.GetIdentityFP(), ecrMsg.GetContents()) + if !has { + jww.INFO.Printf("checking backup %v", preimage.MakeDefault(identity.Source)) + //if it doesnt exist, check against the default fingerprint for the identity + forMe = fingerprint2.CheckIdentityFP(ecrMsg.GetIdentityFP(), + ecrMsg.GetContents(), preimage.MakeDefault(identity.Source)) + } + if !forMe { if jww.GetLogThreshold() == jww.LevelTrace { expectedFP := fingerprint2.IdentityFP(ecrMsg.GetContents(), - identity.Source) + preimage.MakeDefault(identity.Source)) jww.TRACE.Printf("Message for %d (%s) failed identity "+ - "check: %v (expected) vs %v (received)", identity.EphId, + "check: %v (expected-default) vs %v (received)", identity.EphId, identity.Source, expectedFP, ecrMsg.GetIdentityFP()) } diff --git a/network/message/sendCmix.go b/network/message/sendCmix.go index bfb57a56903cb9f4f23e46cae2c59657c0d06da1..5847e4e15631424d0b4165d11d49e0ec980cf98d 100644 --- a/network/message/sendCmix.go +++ b/network/message/sendCmix.go @@ -135,7 +135,7 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message, stream := rng.GetStream() wrappedMsg, encMsg, ephID, err := buildSlotMessage(msg, recipient, - firstGateway, stream, senderId, bestRound, roundKeys) + firstGateway, stream, senderId, bestRound, roundKeys, cmixParams) if err != nil { stream.Close() return 0, ephemeral.Id{}, err diff --git a/network/message/sendCmixUtils.go b/network/message/sendCmixUtils.go index 4e71f5731f775386d9ab3afa1d10316214d67b32..30fb94088c1b339462c87405e7b10e221299f3a2 100644 --- a/network/message/sendCmixUtils.go +++ b/network/message/sendCmixUtils.go @@ -10,6 +10,8 @@ package message import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/interfaces/params" + preimage2 "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/cmix" pb "gitlab.com/elixxir/comms/mixmessages" @@ -119,7 +121,7 @@ func processRound(instance *network.Instance, session *storage.Session, // the recipient. func buildSlotMessage(msg format.Message, recipient *id.ID, target *id.ID, stream *fastRNG.Stream, senderId *id.ID, bestRound *pb.RoundInfo, - roundKeys *cmix.RoundKeys) (*pb.GatewaySlot, format.Message, ephemeral.Id, + roundKeys *cmix.RoundKeys, param params.CMIX) (*pb.GatewaySlot, format.Message, ephemeral.Id, error) { // Set the ephemeral ID @@ -139,11 +141,23 @@ func buildSlotMessage(msg format.Message, recipient *id.ID, target *id.ID, msg.SetEphemeralRID(ephIdFilled[:]) + // use the alternate identity preimage if it is set + var preimage []byte + if param.IdentityPreimage != nil { + preimage = param.IdentityPreimage + jww.INFO.Printf("Sending to %s with override preimage %v", recipient, preimage) + }else{ + preimage = preimage2.MakeDefault(recipient) + jww.INFO.Printf("Sending to %s with default preimage %v", recipient, preimage) + } + // Set the identity fingerprint - ifp := fingerprint.IdentityFP(msg.GetContents(), recipient) + ifp := fingerprint.IdentityFP(msg.GetContents(), preimage) msg.SetIdentityFP(ifp) + jww.INFO.Printf(" Sending to %s with preimage %v, ifp: %v, contents: %v", recipient, preimage, ifp, msg.GetContents()) + // Encrypt the message salt := make([]byte, 32) _, err = stream.Read(salt) diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go index 5f42729f651de44afa707d10b52d2d5c83caf428..52d202393cc8817f2bf9ffd8acace69fb9df72eb 100644 --- a/network/message/sendE2E.go +++ b/network/message/sendE2E.go @@ -93,6 +93,11 @@ func (m *Manager) SendE2E(msg message.Send, param params.E2E, jww.INFO.Printf("E2E sending %d/%d to %s with msgDigest: %s, key fp: %s", i+i, len(partitions), msg.Recipient, msgEnc.Digest(), key.Fingerprint()) + //set the preimage to the default e2e one if it is not already set + if param.IdentityPreimage == nil { + param.IdentityPreimage = partner.GetE2EPreimage() + } + //send the cmix message, each partition in its own thread wg.Add(1) go func(i int) { diff --git a/network/message/sendManyCmix.go b/network/message/sendManyCmix.go index 3c212f745846c2b60747517d3759a0d019e5528d..60267b607bb69a008bf2409e85687549c445f8c4 100644 --- a/network/message/sendManyCmix.go +++ b/network/message/sendManyCmix.go @@ -115,7 +115,7 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, i := 0 for recipient, msg := range msgs { slots[i], encMsgs[i], ephemeralIds[i], err = buildSlotMessage( - msg, &recipient, firstGateway, stream, senderId, bestRound, roundKeys) + msg, &recipient, firstGateway, stream, senderId, bestRound, roundKeys, param) if err != nil { return 0, []ephemeral.Id{}, errors.Errorf("failed to build "+ "slot message for %s: %+v", recipient, err) diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go index a978beeae77c2fffa723c15f495ebf1fa4925c5c..68119395dc81f3c3dff50dfcfdd5b5d8b6da9ea4 100644 --- a/storage/e2e/manager.go +++ b/storage/e2e/manager.go @@ -14,13 +14,13 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/storage/utility" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" dh "gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/xx_network/primitives/id" "golang.org/x/crypto/blake2b" - "sort" ) const managerPrefix = "Manager{partner:%s}" @@ -228,10 +228,22 @@ const relationshipFpLength = 15 // relationship. The fingerprint is a base 64 encoded hash of of the two // relationship fingerprints truncated to 15 characters. func (m *Manager) GetRelationshipFingerprint() string { + + // Base 64 encode hash and truncate + return base64.StdEncoding.EncodeToString(m.GetRelationshipFingerprintBytes())[:relationshipFpLength] +} + +// GetRelationshipFingerprintBytes returns a unique fingerprint for an E2E +// relationship. used for the e2e preimage. +func (m *Manager) GetRelationshipFingerprintBytes() []byte { // Sort fingerprints - fps := [][]byte{m.receive.fingerprint, m.send.fingerprint} - less := func(i, j int) bool { return bytes.Compare(fps[i], fps[j]) == -1 } - sort.Slice(fps, less) + var fps [][]byte + + if bytes.Compare(m.receive.fingerprint, m.send.fingerprint) == 1 { + fps = [][]byte{m.send.fingerprint, m.receive.fingerprint} + } else { + fps = [][]byte{m.receive.fingerprint, m.send.fingerprint} + } // Hash fingerprints h, _ := blake2b.New256(nil) @@ -240,5 +252,17 @@ func (m *Manager) GetRelationshipFingerprint() string { } // Base 64 encode hash and truncate - return base64.StdEncoding.EncodeToString(h.Sum(nil))[:relationshipFpLength] + return h.Sum(nil) +} + +// GetE2EPreimage returns a hash of the unique +// fingerprint for an E2E relationship message. +func (m *Manager) GetE2EPreimage() []byte { + return preimage.Generate(m.GetRelationshipFingerprintBytes(), preimage.E2e) +} + +// GetRekeyPreimage returns a hash of the unique +// fingerprint for an E2E rekey message. +func (m *Manager) GetRekeyPreimage() []byte { + return preimage.Generate(m.GetRelationshipFingerprintBytes(), preimage.Rekey) } diff --git a/storage/edge/edge.go b/storage/edge/edge.go new file mode 100644 index 0000000000000000000000000000000000000000..a720055d73f95dcb8c70455cb16a40a3ba0f81b9 --- /dev/null +++ b/storage/edge/edge.go @@ -0,0 +1,266 @@ +package edge + +import ( + "encoding/json" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage/versioned" + fingerprint2 "gitlab.com/elixxir/crypto/fingerprint" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" + "sync" +) + +// This stores Preimages which can be used with the identity fingerprint system. + +const ( + edgeStorePrefix = "edgeStore" + edgeStoreKey = "edgeStoreKey" + edgeStoreVersion = 0 +) + +type ListUpdateCallBack func(identity *id.ID, deleted bool) + +type Store struct { + kv *versioned.KV + edge map[id.ID]Preimages + callbacks map[id.ID][]ListUpdateCallBack + mux sync.RWMutex +} + +// NewStore creates a new edge store object and inserts the default Preimages +// for the base identity. +func NewStore(kv *versioned.KV, baseIdentity *id.ID) (*Store, error) { + kv = kv.Prefix(edgeStorePrefix) + + s := &Store{ + kv: kv, + edge: make(map[id.ID]Preimages), + callbacks: make(map[id.ID][]ListUpdateCallBack), + } + + defaultPreimages := newPreimages(baseIdentity) + err := defaultPreimages.save(kv, baseIdentity) + if err != nil { + return nil, errors.WithMessage(err, "Failed to create preimage store, "+ + "failed to create default Preimages") + } + + s.edge[*baseIdentity] = defaultPreimages + + return s, s.save() +} + +// Add adds the Preimage to the list of the given identity and calls any +// associated callbacks. +func (s *Store) Add(preimage Preimage, identity *id.ID) { + s.mux.Lock() + defer s.mux.Unlock() + + // Get the list to update, create if needed + preimages, exists := s.edge[*identity] + if !exists { + preimages = newPreimages(identity) + } + + // Add to the list + if !preimages.add(preimage) { + return + } + + // Store the updated list + if err := preimages.save(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to store preimages list after adding "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + // Update the map + s.edge[*identity] = preimages + if !exists { + err := s.save() + if err != nil { + jww.FATAL.Panicf("Failed to store edge store after adding "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + } + + // Call any callbacks to notify + for _, cb := range s.callbacks[*identity] { + go cb(identity, false) + } + + return +} + +// Remove deletes the preimage for the given identity and triggers the +// associated callback. If the given preimage is the last in the Preimages list, +// then the entire list is removed and the associated callback will be triggered +// with the boolean indicating the list was deleted. +func (s *Store) Remove(preimage Preimage, identity *id.ID) error { + s.mux.Lock() + defer s.mux.Unlock() + + preimages, exists := s.edge[*identity] + if !exists { + return errors.Errorf("cannot delete preimage %v from identity %s; "+ + "identity cannot be found", preimage.Data, identity) + } + + preimages.remove(preimage.Data) + + if len(preimages) == 0 { + delete(s.edge, *identity) + if err := s.save(); err != nil { + jww.FATAL.Panicf("Failed to store edge store after removing "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + if err := preimages.delete(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to delete preimage list store after "+ + "removing preimage %v to identity %s: %+v", preimage.Data, + identity, err) + } + + // Call any callbacks to notify + for i := range s.callbacks[*identity] { + cb := s.callbacks[*identity][i] + go cb(identity, true) + } + + return nil + } + + if err := preimages.save(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to store preimage list store after removing "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + s.edge[*identity] = preimages + + // Call any callbacks to notify + for i := range s.callbacks[*identity] { + cb := s.callbacks[*identity][i] + go cb(identity, false) + } + + return nil +} + +// Get returns the Preimages list for the given identity. +func (s *Store) Get(identity *id.ID) ([]Preimage, bool) { + s.mux.RLock() + defer s.mux.RUnlock() + + preimages, exists := s.edge[*identity] + if !exists { + return nil, false + } + + preiamgesSlice := make([]Preimage, 0, len(preimages)) + + for _, preimage := range preimages { + preiamgesSlice = append(preiamgesSlice, preimage) + } + return preiamgesSlice, exists +} + +// Check looks checks if the identity fingerprint matches for any of +// the stored preimages. It returns the preimage it hit with if it +// finds one. +func (s *Store) Check(identity *id.ID, identityFP []byte, messageContents []byte) (bool, bool, Preimage) { + s.mux.RLock() + defer s.mux.RUnlock() + + preimages, exists := s.edge[*identity] + if !exists { + return false, false, Preimage{} + } + + for _, preimage := range preimages { + jww.INFO.Printf("checking ifp: %v, msg: %v, preimage %v", identityFP, messageContents, preimage) + if fingerprint2.CheckIdentityFP(identityFP, messageContents, preimage.Data) { + return true, true, preimage + } + } + + return true, false, Preimage{} +} + +// AddUpdateCallback adds the callback to be called for changes to the identity. +func (s *Store) AddUpdateCallback(identity *id.ID, luCB ListUpdateCallBack) { + s.mux.Lock() + defer s.mux.Unlock() + + s.callbacks[*identity] = append(s.callbacks[*identity], luCB) +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +func LoadStore(kv *versioned.KV) (*Store, error) { + kv = kv.Prefix(edgeStorePrefix) + + // Load the list of identities with preimage lists + obj, err := kv.Get(edgeStoreKey, preimageStoreVersion) + if err != nil { + return nil, errors.WithMessagef(err, "failed to load edge store") + } + + identities := make([]id.ID, 0) + + err = json.Unmarshal(obj.Data, &identities) + if err != nil { + return nil, errors.WithMessagef(err, "failed to unmarshal edge store") + } + + s := &Store{ + kv: kv, + edge: make(map[id.ID]Preimages), + callbacks: make(map[id.ID][]ListUpdateCallBack), + } + + // Load the preimage lists for all identities + for i := range identities { + eid := &identities[i] + + preimages, err := loadPreimages(kv, eid) + if err != nil { + return nil, err + } + + s.edge[*eid] = preimages + } + + return s, nil +} + +func (s *Store) save() error { + identities := make([]id.ID, 0, len(s.edge)) + + for eid := range s.edge { + identities = append(identities, eid) + } + + // JSON marshal + data, err := json.Marshal(&identities) + if err != nil { + return errors.WithMessagef(err, "Failed to marshal edge list for "+ + "storage") + } + + // Construct versioning object + obj := versioned.Object{ + Version: edgeStoreVersion, + Timestamp: netTime.Now(), + Data: data, + } + + // Save to storage + err = s.kv.Set(edgeStoreKey, preimageStoreVersion, &obj) + if err != nil { + return errors.WithMessagef(err, "Failed to store edge list") + } + + return nil +} diff --git a/storage/edge/edge_test.go b/storage/edge/edge_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e059e749666f0f30223e78e584a5af995a91092a --- /dev/null +++ b/storage/edge/edge_test.go @@ -0,0 +1,619 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package edge + +import ( + "encoding/json" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/versioned" + fingerprint2 "gitlab.com/elixxir/crypto/fingerprint" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/id" + "math/rand" + "reflect" + "sync" + "testing" + "time" +) + +// Tests that NewStore returns the expected new Store and that it can be loaded +// from storage. +func TestNewStore(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + baseIdentity := id.NewIdFromString("baseIdentity", id.User, t) + expected := &Store{ + kv: kv.Prefix(edgeStorePrefix), + edge: map[id.ID]Preimages{*baseIdentity: newPreimages(baseIdentity)}, + callbacks: make(map[id.ID][]ListUpdateCallBack), + } + + received, err := NewStore(kv, baseIdentity) + if err != nil { + t.Errorf("NewStore returned an error: %+v", err) + } + + if !reflect.DeepEqual(expected, received) { + t.Errorf("New Store does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } + + _, err = expected.kv.Get(preimagesKey(baseIdentity), preimageStoreVersion) + if err != nil { + t.Errorf("Failed to load Store from storage: %+v", err) + } +} + +// Adds three Preimage to the store, two with the same identity. It checks that +// Store.Add adds all three exist and that the length of the list is correct. +// Also checks that the appropriate callbacks are called. +func TestStore_Add(t *testing.T) { + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + var wg sync.WaitGroup + + id0Chan := make(chan struct { + identity *id.ID + deleted bool + }, 2) + s.callbacks[*identities[0]] = []ListUpdateCallBack{ + func(identity *id.ID, deleted bool) { + id0Chan <- struct { + identity *id.ID + deleted bool + }{identity: identity, deleted: deleted} + }} + + wg.Add(1) + wg.Add(1) + go func() { + for i := 0; i < 2; i++ { + select { + case <-time.NewTimer(50 * time.Millisecond).C: + t.Errorf("Timed out waiting for callback (%d).", i) + case r := <-id0Chan: + if !identities[0].Cmp(r.identity) { + t.Errorf("Received wrong identity (%d).\nexpected: %s"+ + "\nreceived: %s", i, identities[0], r.identity) + } else if r.deleted == true { + t.Errorf("Received wrong value for deleted (%d)."+ + "\nexpected: %t\nreceived: %t", i, true, r.deleted) + } + } + wg.Done() + } + }() + + id1Chan := make(chan struct { + identity *id.ID + deleted bool + }) + s.callbacks[*identities[1]] = []ListUpdateCallBack{ + func(identity *id.ID, deleted bool) { + id1Chan <- struct { + identity *id.ID + deleted bool + }{identity: identity, deleted: deleted} + }} + + wg.Add(1) + go func() { + select { + case <-time.NewTimer(10 * time.Millisecond).C: + t.Errorf("Timed out waiting for callback.") + case r := <-id1Chan: + if !identities[1].Cmp(r.identity) { + t.Errorf("Received wrong identity.\nexpected: %s\nreceived: %s", + identities[1], r.identity) + } else if r.deleted == true { + t.Errorf("Received wrong value for deleted."+ + "\nexpected: %t\nreceived: %t", true, r.deleted) + } + } + wg.Done() + }() + + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + if len(s.edge) != 3 { + t.Errorf("Length of edge incorrect.\nexpected: %d\nreceived: %d", + 3, len(s.edge)) + } + + pis := s.edge[*identities[0]] + + if len(pis) != 3 { + t.Errorf("Length of preimages for identity %s inocrrect."+ + "\nexpected: %d\nreceived: %d", identities[0], 3, len(pis)) + } + + expected := Preimage{preimage.Generate(identities[0].Bytes(), preimage.Default), preimage.Default, identities[0].Bytes()} + if !reflect.DeepEqual(pis[expected.key()], expected) { + t.Errorf("First Preimage of first Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, pis[expected.key()]) + } + + expected = preimages[0] + if !reflect.DeepEqual(pis[expected.key()], expected) { + t.Errorf("Second Preimage of first Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, pis[expected.key()]) + } + + expected = preimages[2] + if !reflect.DeepEqual(pis[expected.key()], expected) { + t.Errorf("Third Preimage of first Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, pis[expected.key()]) + } + + pis = s.edge[*identities[1]] + + if len(pis) != 2 { + t.Errorf("Length of preimages for identity %s inocrrect."+ + "\nexpected: %d\nreceived: %d", identities[1], 2, len(pis)) + } + + expected = Preimage{preimage.Generate(identities[1].Bytes(), preimage.Default), preimage.Default, identities[1].Bytes()} + if !reflect.DeepEqual(pis[expected.key()], expected) { + t.Errorf("First Preimage of second Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, pis[expected.key()]) + } + + expected = preimages[1] + if !reflect.DeepEqual(pis[expected.key()], expected) { + t.Errorf("Second Preimage of second Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, pis[expected.key()]) + } + + wg.Wait() +} + +// Adds three Preimage to two identities and tests that Store.Remove removes all +// three blue the default preimage for the second identity and checks that all +// Preimage have been deleted, that the Preimages for the second identity has +// been deleted and that the callbacks are called with the expected values. +func TestStore_Remove(t *testing.T) { + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + var wg sync.WaitGroup + + id0Chan := make(chan struct { + identity *id.ID + deleted bool + }, 2) + s.callbacks[*identities[0]] = []ListUpdateCallBack{ + func(identity *id.ID, deleted bool) { + id0Chan <- struct { + identity *id.ID + deleted bool + }{identity: identity, deleted: deleted} + }} + + wg.Add(1) + wg.Add(1) + go func() { + for i := 0; i < 2; i++ { + select { + case <-time.NewTimer(50 * time.Millisecond).C: + t.Errorf("Timed out waiting for callback (%d).", i) + case r := <-id0Chan: + if !identities[0].Cmp(r.identity) { + t.Errorf("Received wrong identity (%d).\nexpected: %s"+ + "\nreceived: %s", i, identities[0], r.identity) + } else if r.deleted == true { + t.Errorf("Received wrong value for deleted (%d)."+ + "\nexpected: %t\nreceived: %t", i, true, r.deleted) + } + } + wg.Done() + } + }() + + id1Chan := make(chan struct { + identity *id.ID + deleted bool + }) + s.callbacks[*identities[1]] = []ListUpdateCallBack{ + func(identity *id.ID, deleted bool) { + id1Chan <- struct { + identity *id.ID + deleted bool + }{identity: identity, deleted: deleted} + }} + + wg.Add(1) + wg.Add(1) + go func() { + for i := 0; i < 2; i++ { + select { + case <-time.NewTimer(50 * time.Millisecond).C: + t.Errorf("Timed out waiting for callback (%d).", i) + case r := <-id1Chan: + if !identities[1].Cmp(r.identity) { + t.Errorf("Received wrong identity (%d).\nexpected: %s"+ + "\nreceived: %s", i, identities[1], r.identity) + } + } + wg.Done() + } + }() + + err := s.Remove(preimages[0], identities[0]) + if err != nil { + t.Errorf("Remove returned an error: %+v", err) + } + + err = s.Remove(preimages[1], identities[1]) + if err != nil { + t.Errorf("Remove returned an error: %+v", err) + } + + err = s.Remove(Preimage{Data: identities[1].Bytes()}, identities[1]) + if err != nil { + t.Errorf("Remove returned an error: %+v", err) + } + + err = s.Remove(preimages[2], identities[0]) + if err != nil { + t.Errorf("Remove returned an error: %+v", err) + } + + if len(s.edge) != 3 { + t.Errorf("Length of edge incorrect.\nexpected: %d\nreceived: %d", + 2, len(s.edge)) + } + + pis := s.edge[*identities[0]] + + if len(pis) != 1 { + t.Errorf("Length of preimages for identity %s inocrrect."+ + "\nexpected: %d\nreceived: %d", identities[0], 1, len(pis)) + } + + expected := preimages[0] + if _, exists := pis[expected.key()]; exists { + t.Errorf("Second Preimage of first Preimages exists when it should " + + "have been deleted.") + } + + expected = preimages[2] + if _, exists := pis[expected.key()]; exists { + t.Errorf("Third Preimage of first Preimages exists when it should " + + "have been deleted.") + } + + pis = s.edge[*identities[1]] + + if len(pis) != 1 { + t.Errorf("Length of preimages for identity %s inocrrect."+ + "\nexpected: %d\nreceived: %d", identities[1], 1, len(pis)) + } + + wg.Wait() +} + +// Tests that Store.Get returns the expected Preimages. +func TestStore_Get(t *testing.T) { + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + pis, exists := s.Get(identities[0]) + if !exists { + t.Errorf("No Preimages found for identity %s.", identities[0]) + } + + expected := []Preimage{ + {preimage.Generate(identities[0].Bytes(), preimage.Default), preimage.Default, identities[0].Bytes()}, + preimages[0], + preimages[2], + } + + if len(expected) != len(pis) { + t.Errorf("First Preimages for identity %s does not match expected, difrent lengths of %d and %d"+ + "\nexpected: %+v\nreceived: %+v", identities[0], len(expected), len(pis), expected, pis) + } + +top: + for i, lookup := range expected { + for _, checked := range pis { + if reflect.DeepEqual(lookup, checked) { + continue top + } + } + t.Errorf("Entree %d in expected %v not found in received %v", i, lookup, pis) + } + + pis, exists = s.Get(identities[1]) + if !exists { + t.Errorf("No Preimages found for identity %s.", identities[1]) + } + + expected = []Preimage{ + {preimage.Generate(identities[1].Bytes(), preimage.Default), preimage.Default, identities[1].Bytes()}, + preimages[1], + } + + if len(expected) != len(pis) { + t.Errorf("First Preimages for identity %s does not match expected, difrent lengths of %d and %d"+ + "\nexpected: %+v\nreceived: %+v", identities[0], len(expected), len(pis), expected, pis) + } + +top2: + for i, lookup := range expected { + for _, checked := range pis { + if reflect.DeepEqual(lookup, checked) { + continue top2 + } + } + t.Errorf("Entree %d in expected %v not found in received %v", i, lookup, pis) + } +} + +// Tests that Store.AddUpdateCallback adds all the appropriate callbacks for +// each identity by calling each callback and checking if the received identity +// is correct. +func TestStore_AddUpdateCallback(t *testing.T) { + s, _, _ := newTestStore(t) + // Create list of n identities, each with one more callback than the last + // with the first having one + n := 3 + chans := make(map[id.ID][]chan *id.ID, n) + for i := 0; i < n; i++ { + identity := id.NewIdFromUInt(uint64(i), id.User, t) + chans[*identity] = make([]chan *id.ID, i+1) + for j := range chans[*identity] { + cbChan := make(chan *id.ID, 2) + cb := func(cbIdentity *id.ID, _ bool) { cbChan <- cbIdentity } + chans[*identity][j] = cbChan + s.AddUpdateCallback(identity, cb) + } + } + + var wg sync.WaitGroup + for identity, chanList := range chans { + for i := range chanList { + wg.Add(1) + go func(identity *id.ID, i int) { + select { + case <-time.NewTimer(150 * time.Millisecond).C: + t.Errorf("Timed out waiting on callback %d/%d for "+ + "identity %s.", i+1, len(chans[*identity]), identity) + case r := <-chans[*identity][i]: + if !identity.Cmp(r) { + t.Errorf("Identity received from callback %d/%d does "+ + "not match expected.\nexpected: %s\nreceived: %s", + i+1, len(chans[*identity]), identity, r) + } + } + wg.Done() + }(identity.DeepCopy(), i) + } + } + + for identity, cbs := range chans { + for i := range cbs { + go s.callbacks[identity][i](identity.DeepCopy(), false) + } + } + + wg.Wait() +} + +func TestLoadStore(t *testing.T) { + // Initialize store + s, kv, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + // Add preimages + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + err := s.save() + if err != nil { + t.Fatalf("save error: %v", err) + } + + receivedStore, err := LoadStore(kv) + if err != nil { + t.Fatalf("LoadStore error: %v", err) + } + + expectedPis := [][]Preimage{ + { + Preimage{preimage.Generate(identities[0].Bytes(), preimage.Default), preimage.Default, identities[0].Bytes()}, + preimages[0], + preimages[2], + }, + { + Preimage{preimage.Generate(identities[1].Bytes(), preimage.Default), preimage.Default, identities[1].Bytes()}, + preimages[1], + }, + } + + for i, identity := range identities { + pis, exists := receivedStore.Get(identity) + if !exists { + t.Errorf("Identity %s does not exist in loaded store", identity) + } + + if len(expectedPis[i]) != len(pis) { + t.Errorf("First Preimages for identity %s does not match expected, difrent lengths of %d and %d"+ + "\nexpected: %+v\nreceived: %+v", identities[0], len(expectedPis[i]), len(pis), expectedPis[i], pis) + } + + top: + for idx, lookup := range expectedPis[i] { + for _, checked := range pis { + if reflect.DeepEqual(lookup, checked) { + continue top + } + } + t.Errorf("Entree %d in expected %v not found in received %v", idx, lookup, pis) + } + + } +} + +func TestStore_Check(t *testing.T) { + // Initialize store + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + // Add preimages + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + testMsg := []byte("test message 123") + preImageData := preimages[0].Data + testFp := fingerprint2.IdentityFP(testMsg, preImageData) + + has, forMe, receivedPreImage := s.Check(identities[0], testFp, testMsg) + + if !has || !forMe || !reflect.DeepEqual(receivedPreImage, preimages[0]) { + t.Errorf("Unexpected result from Check()."+ + "\nExpected results: (has: %v) "+ + "\n\t(forMe: %v)"+ + "\n\t(Preimage: %v)"+ + "\nReceived results: (has: %v) "+ + "\n\t(forME: %v)"+ + "\n\t(Preimage: %v)", true, true, preimages[0], + has, forMe, receivedPreImage) + } + + // Check with wrong identity (has should be true, for me false) + has, forMe, _ = s.Check(identities[1], testFp, testMsg) + if !has || forMe { + t.Errorf("Unexpected results from check."+ + "\nExpected results: (has: %v)"+ + "\n\t(ForMe %v)"+ + "\nReceived results: "+ + "has: %v"+ + "\n\t(ForMe: %v)", true, false, has, forMe) + } + +} + +func TestStore_save(t *testing.T) { + // Initialize store + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + + // Save data to KV + err := s.save() + if err != nil { + t.Fatalf("save error: %v", err) + } + + // Manually pull from KV + vo, err := s.kv.Get(edgeStoreKey, preimageStoreVersion) + if err != nil { + t.Fatalf("Failed to retrieve from KV: %v", err) + } + + receivedIdentities := make([]id.ID, 0) + err = json.Unmarshal(vo.Data, &receivedIdentities) + if err != nil { + t.Fatalf("JSON unmarshal error: %v", err) + } + + for _, receivedId := range receivedIdentities { + _, exists := s.Get(&receivedId) + if !exists { + t.Fatalf("Identity retrieved from store does not match " + + "identity stored in") + } + } +} + +// newTestStore creates a new Store with a random base identity. Returns the +// Store, KV, and base identity. +func newTestStore(t *testing.T) (*Store, *versioned.KV, *id.ID) { + kv := versioned.NewKV(make(ekv.Memstore)) + baseIdentity, err := id.NewRandomID( + rand.New(rand.NewSource(time.Now().Unix())), id.User) + if err != nil { + t.Fatalf("Failed to generate random base identity: %+v", err) + } + + s, err := NewStore(kv, baseIdentity) + if err != nil { + t.Fatalf("Failed to create new test Store: %+v", err) + } + + return s, kv, baseIdentity +} diff --git a/storage/edge/preimage.go b/storage/edge/preimage.go new file mode 100644 index 0000000000000000000000000000000000000000..a8e4c89432dddc25596495fe69b59e7786ceab45 --- /dev/null +++ b/storage/edge/preimage.go @@ -0,0 +1,128 @@ +package edge + +import ( + "encoding/base64" + "encoding/json" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" +) + +const ( + preimageStoreKey = "preimageStoreKey" + preimageStoreVersion = 0 +) + +type Preimage struct { + Data []byte + Type string + Source []byte +} + +// key returns the key used to identify the Preimage in a map. +func (pi Preimage) key() string { + return base64.StdEncoding.EncodeToString(pi.Data) +} + +// Preimages is a map of unique Preimage keyed on their Data. +type Preimages map[string]Preimage + +// newPreimages makes a Preimages object for the given identity and populates +// it with the default preimage for the identity. Does not store to disk. +func newPreimages(identity *id.ID) Preimages { + defaultPreimage := Preimage{ + Data: preimage.MakeDefault(identity), + Type: preimage.Default, + Source: identity[:], + } + pis := Preimages{ + defaultPreimage.key(): defaultPreimage, + } + + return pis +} + +// add adds the preimage to the list. +func (pis Preimages) add(preimage Preimage) bool { + if _, exists := pis[preimage.key()]; exists { + return false + } + + pis[preimage.key()] = preimage + + return true +} + +// remove deletes the Preimage with the matching data from the list. +func (pis Preimages) remove(data []byte) { + key := base64.StdEncoding.EncodeToString(data) + delete(pis, key) +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +// loadPreimages loads a Preimages object for the given identity. +func loadPreimages(kv *versioned.KV, identity *id.ID) (Preimages, error) { + + // Get the data from storage + obj, err := kv.Get(preimagesKey(identity), preimageStoreVersion) + if err != nil { + return nil, errors.WithMessagef(err, "Failed to load edge Preimages "+ + "for identity %s", identity) + } + + var preimageList Preimages + err = json.Unmarshal(obj.Data, &preimageList) + if err != nil { + return nil, errors.WithMessagef(err, "failed to unmarshal edge "+ + "Preimages for identity %s", identity) + } + + return preimageList, nil +} + +// save stores the preimage list to disk. +func (pis Preimages) save(kv *versioned.KV, identity *id.ID) error { + // JSON marshal + data, err := json.Marshal(&pis) + if err != nil { + return errors.WithMessagef(err, "Failed to marshal Preimages list "+ + "for stroage for identity %s", identity) + } + + // Construct versioning object + obj := versioned.Object{ + Version: preimageStoreVersion, + Timestamp: netTime.Now(), + Data: data, + } + + // Save to storage + err = kv.Set(preimagesKey(identity), preimageStoreVersion, &obj) + if err != nil { + return errors.WithMessagef(err, "Failed to store Preimages list for "+ + "identity %s", identity) + } + + return nil +} + +// delete removes the Preimages from storage. +func (pis Preimages) delete(kv *versioned.KV, identity *id.ID) error { + err := kv.Delete(preimagesKey(identity), preimageStoreVersion) + if err != nil { + return errors.WithMessagef(err, "Failed to delete Preimages list for "+ + "identity %s", identity) + } + + return nil +} + +// preimagesKey generates the key for saving a Preimages to storage. +func preimagesKey(identity *id.ID) string { + return preimageStoreKey + ":" + identity.String() +} diff --git a/storage/edge/preimage_test.go b/storage/edge/preimage_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b31cc4274075344ad705b5308e7ac30e93bac5d0 --- /dev/null +++ b/storage/edge/preimage_test.go @@ -0,0 +1,246 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package edge + +import ( + "bytes" + "encoding/json" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" +) + +// Tests that newPreimages returns the expected new Preimages. +func Test_newPreimages(t *testing.T) { + identity := id.NewIdFromString("identity", id.User, t) + pimg := Preimage{ + Data: preimage.MakeDefault(identity), + Type: "default", + Source: identity.Bytes(), + } + expected := Preimages{ + pimg.key(): pimg, + } + + received := newPreimages(identity) + + if !reflect.DeepEqual(expected, received) { + t.Errorf("New Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } +} + +// Tests that Preimages.add adds the expected Preimage to the list. +func TestPreimages_add(t *testing.T) { + identity0 := id.NewIdFromString("identity0", id.User, t) + identity1 := id.NewIdFromString("identity1", id.User, t) + identity2 := id.NewIdFromString("identity3", id.User, t) + expected := Preimages{ + identity0.String(): {preimage.Generate(identity0.Bytes(), preimage.Default), preimage.Default, preimage.MakeDefault(identity0)}, + identity1.String(): {preimage.Generate(identity1.Bytes(), preimage.Group), preimage.Group, identity1.Bytes()}, + identity2.String(): {preimage.Generate(identity2.Bytes(), preimage.Default), preimage.Default, identity2.Bytes()}, + } + + pis := newPreimages(identity0) + preimageOne := Preimage{preimage.Generate(identity1.Bytes(), preimage.Group), preimage.Group, identity1.Bytes()} + exists := pis.add(preimageOne) + if !exists { + t.Errorf("Failed to add idenetity.") + } + + preimageTwo := Preimage{preimage.Generate(identity2.Bytes(), preimage.Default), preimage.Default, identity2.Bytes()} + exists = pis.add(preimageTwo) + if !exists { + t.Errorf("Failed to add idenetity.") + } + + for identity, pimg := range expected { + if _, exists = pis[pimg.key()]; !exists { + t.Errorf("Identity %s could not be found", identity) + } + } + + expectedPreimageIdentityTwo := Preimage{ + Data: preimage.Generate(identity2.Bytes(), preimage.Default), + Type: preimage.Default, + Source: identity2.Bytes(), + } + // Test that nothing happens when a Preimage with the same data exists + exists = pis.add(Preimage{preimage.Generate(identity2.Bytes(), preimage.Default), "test", identity2.Bytes()}) + if exists { + t.Errorf("Add idenetity that shoudl already exist.") + } + + receivedPreimageIdentityTwo := pis[preimageTwo.key()] + + if !reflect.DeepEqual(expectedPreimageIdentityTwo, receivedPreimageIdentityTwo) { + t.Errorf("Unexpected overwritting of existing identity") + } + +} + +// Tests that Preimages.remove removes all the correct Preimage from the list. +func TestPreimages_remove(t *testing.T) { + pis := make(Preimages) + var identities [][]byte + + // Add 10 Preimage to the list + for i := 0; i < 10; i++ { + identity := id.NewIdFromUInt(uint64(i), id.User, t) + pisType := preimage.Default + if i%2 == 0 { + pisType = preimage.Group + } + + exists := pis.add(Preimage{identity.Bytes(), pisType, identity.Bytes()}) + if !exists { + t.Errorf("Failed to add idenetity.") + } + identities = append(identities, identity.Bytes()) + } + + // Remove each Preimage, check if the length of the list has changed, and + // check that the correct Preimage was removed + for i, identity := range identities { + pis.remove(identity) + + if len(pis) != len(identities)-(i+1) { + t.Errorf("Length of Preimages incorrect after removing %d Premiages."+ + "\nexpected: %d\nreceived: %d", i, len(identities)-(i+1), + len(pis)) + } + + // Check if the correct Preimage was deleted + for _, pimg := range pis { + if bytes.Equal(pimg.Data, identity) { + t.Errorf("Failed to delete Preimage #%d: %+v", i, pimg) + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +// Tests that the Preimages loaded via loadPreimages matches the original saved +// to storage. +func Test_loadPreimages(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("Failed to save Preimages to storage: %+v", err) + } + + loaded, err := loadPreimages(kv, identity) + if err != nil { + t.Errorf("loadPreimages returned an error: %+v", err) + } + + if !reflect.DeepEqual(pis, loaded) { + t.Errorf("Loaded Preimages do not match original."+ + "\nexpected: %+v\nreceived: %+v", pis, loaded) + } +} + +// Tests that the data saved to storage via Preimages.save can be loaded and +// unmarshalled and that it matches the original. +func TestPreimages_save(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("save returned an error: %+v", err) + } + + obj, err := kv.Get(preimagesKey(identity), preimageStoreVersion) + if err != nil { + t.Errorf("Failed to load Preimages from storage: %+v", err) + } + + var loaded Preimages + err = json.Unmarshal(obj.Data, &loaded) + if err != nil { + t.Errorf("Failed to unmarshal Preimages loaded from storage: %+v", err) + } + + if !reflect.DeepEqual(pis, loaded) { + t.Errorf("Loaded Preimages do not match original."+ + "\nexpected: %+v\nreceived: %+v", pis, loaded) + } +} + +// Tests that Preimages.delete deletes the Preimages saved to storage by +// attempting to load them. +func TestPreimages_delete(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("Failed to save Preimages to storage: %+v", err) + } + + err = pis.delete(kv, identity) + if err != nil { + t.Errorf("delete returned an error: %+v", err) + } + + loaded, err := loadPreimages(kv, identity) + if err == nil { + t.Errorf("loadPreimages loaded a Preimages from storage when it "+ + "should have been deleted: %+v", loaded) + } +} + +// Consistency test: tests that preimagesKey returned the expected output for a +// set input. +func Test_preimagesKey(t *testing.T) { + expectedKeys := []string{ + "preimageStoreKey:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ACOG8m/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AEcN5N+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AGqU109DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AI4byb8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ALGivC7FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ANUprp6GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:APiwoQ5HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ARw3k34IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AT++he3JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + } + + for i, expected := range expectedKeys { + identity := id.NewIdFromUInt(uint64(i)*1e16, id.User, t) + key := preimagesKey(identity) + if key != expected { + t.Errorf("Key #%d does not match expected."+ + "\nexpected: %q\nreceived: %q", i, expected, key) + } + } +} diff --git a/storage/session.go b/storage/session.go index fd98a85c22e1d1c3aea073ad3d1d9d6a709c6e1c..4a380b7911699490b0d6437beaa84c911b3ba080 100644 --- a/storage/session.go +++ b/storage/session.go @@ -10,6 +10,7 @@ package storage import ( + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/hostList" "gitlab.com/elixxir/client/storage/rounds" "sync" @@ -68,6 +69,7 @@ type Session struct { clientVersion *clientVersion.Store uncheckedRounds *rounds.UncheckedRoundStore hostList *hostList.Store + edgeCheck *edge.Store } // Initialize a new Session object @@ -154,6 +156,10 @@ func New(baseDir, password string, u userInterface.User, currentVersion version. s.hostList = hostList.NewStore(s.kv) + s.edgeCheck, err = edge.NewStore(s.kv, u.ReceptionID) + if err != nil { + return nil, errors.WithMessage(err, "Failed to edge check store") + } return s, nil } @@ -232,6 +238,11 @@ func Load(baseDir, password string, currentVersion version.Version, s.hostList = hostList.NewStore(s.kv) + s.edgeCheck, err = edge.LoadStore(s.kv) + if err != nil { + return nil, errors.WithMessage(err, "Failed to load edge check store") + } + return s, nil } @@ -314,6 +325,13 @@ func (s *Session) HostList() *hostList.Store { return s.hostList } +// GetEdge returns the edge preimage store. +func (s *Session) GetEdge() *edge.Store { + s.mux.RLock() + defer s.mux.RUnlock() + return s.edgeCheck +} + // Get an object from the session func (s *Session) Get(key string) (*versioned.Object, error) { return s.kv.Get(key, currentSessionVersion) @@ -417,5 +435,10 @@ func InitTestingSession(i interface{}) *Session { s.hostList = hostList.NewStore(s.kv) + s.edgeCheck, err = edge.NewStore(s.kv, uid) + if err != nil { + jww.FATAL.Panicf("Failed to create new edge Store: %+v", err) + } + return s } diff --git a/ud/lookup.go b/ud/lookup.go index c5d8b4dd96757ec69b52e629ad69c131b92ed7aa..2dc2dd2a8724227423df2ff028809746aa49cc8f 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -23,9 +23,6 @@ type lookupCallback func(contact.Contact, error) // system or returns by the timeout. func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout) - if !m.IsRegistered() { - return errors.New("Failed to lookup: client is not registered.") - } // Build the request and marshal it request := &LookupSend{UserID: uid.Marshal()} diff --git a/ud/search.go b/ud/search.go index 83f33901ca97938c4d74966f11d3909fdd227061..e8db5749ace91b445f56b7f71ace9c8b5165b886 100644 --- a/ud/search.go +++ b/ud/search.go @@ -28,9 +28,6 @@ type searchCallback func([]contact.Contact, error) // of information is known. func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout) - if !m.IsRegistered() { - return errors.New("Failed to search: client is not registered.") - } factHashes, factMap := hashFactList(list)