diff --git a/Makefile b/Makefile index 25edc0f05d5333c3c5bc541fd4a3cec93aebf485..360d733fa356d89f6cf4e12ce681a235a6e9baa3 100644 --- a/Makefile +++ b/Makefile @@ -34,4 +34,4 @@ update_master: master: update_master clean build version -release: update_release clean build version +release: update_release clean build 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 31933912c8eac7519187148e62097e0e98d915e5..8a8fa597878365eb924b84d777b423bb44390a29 100644 --- a/api/client.go +++ b/api/client.go @@ -14,12 +14,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" @@ -77,7 +79,7 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode string) error { jww.INFO.Printf("NewClient(dir: %s)", storageDir) // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) // Parse the NDF def, err := parseNDF(ndfJSON) @@ -109,7 +111,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password []byte) error { jww.INFO.Printf("NewPrecannedClient()") // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) rngStream := rngStreamGen.GetStream() // Parse the NDF @@ -139,7 +141,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error { jww.INFO.Printf("NewVanityClient()") // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) rngStream := rngStreamGen.GetStream() // Parse the NDF @@ -165,7 +167,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) { jww.INFO.Printf("OpenClient()") // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) // Get current client version currentVersion, err := version.ParseVersion(SEMVER) @@ -645,13 +647,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 } @@ -784,6 +818,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/api/mnemonic.go b/api/mnemonic.go index c161aea60c64b1b2079c908714560a5ac6349de0..9b262da82c81bebcc01fd185adbc5d60a196cb6b 100644 --- a/api/mnemonic.go +++ b/api/mnemonic.go @@ -24,7 +24,7 @@ const mnemonicFile = ".recovery" // This encrypted data saved in storage. func StoreSecretWithMnemonic(secret []byte, path string) (string, error) { // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) - rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG).GetStream() + rng := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG).GetStream() // Ensure path is appended by filepath separator "/" if !strings.HasSuffix(path, string(filepath.Separator)) { 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/dummy.go b/bindings/dummy.go new file mode 100644 index 0000000000000000000000000000000000000000..8cbaa84dc6a26f8b28765b5334b5be1bd6fe5df3 --- /dev/null +++ b/bindings/dummy.go @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "gitlab.com/elixxir/client/dummy" + "time" +) + +// StartDummyTraffic starts sending dummy traffic. The maxNumMessages is the +// upper bound of the random number of messages sent each send. avgSendDeltaMS +// is the average duration, in milliseconds, to wait between sends. Sends occur +// every avgSendDeltaMS +/- a random duration with an upper bound of +// randomRangeMS. +func StartDummyTraffic(client *Client, maxNumMessages, avgSendDeltaMS, + randomRangeMS int) error { + avgSendDelta := time.Duration(avgSendDeltaMS) * time.Millisecond + randomRange := time.Duration(randomRangeMS) * time.Millisecond + + m := dummy.NewManager( + maxNumMessages, avgSendDelta, randomRange, &client.api) + + return client.api.AddService(m.StartDummyTraffic) +} diff --git a/bindings/group.go b/bindings/group.go index 00c45668e08563488884f68e5c9f76b2a0907687..eeaba606275e3ff18859163e8257b464ae86bd92 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -9,10 +9,12 @@ package bindings import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gc "gitlab.com/elixxir/client/groupChat" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/crypto/group" "gitlab.com/xx_network/primitives/id" + "time" ) // GroupChat object contains the group chat manager. @@ -23,66 +25,67 @@ type GroupChat struct { // GroupRequestFunc contains a function callback that is called when a group // request is received. type GroupRequestFunc interface { - GroupRequestCallback(g Group) + GroupRequestCallback(g *Group) } // GroupReceiveFunc contains a function callback that is called when a group // message is received. type GroupReceiveFunc interface { - GroupReceiveCallback(msg GroupMessageReceive) + GroupReceiveCallback(msg *GroupMessageReceive) } // NewGroupManager creates a new group chat manager. func NewGroupManager(client *Client, requestFunc GroupRequestFunc, - receiveFunc GroupReceiveFunc) (GroupChat, error) { + receiveFunc GroupReceiveFunc) (*GroupChat, error) { requestCallback := func(g gs.Group) { - requestFunc.GroupRequestCallback(Group{g}) + requestFunc.GroupRequestCallback(&Group{g}) } receiveCallback := func(msg gc.MessageReceive) { - receiveFunc.GroupReceiveCallback(GroupMessageReceive{msg}) + receiveFunc.GroupReceiveCallback(&GroupMessageReceive{msg}) } // Create a new group chat manager m, err := gc.NewManager(&client.api, requestCallback, receiveCallback) if err != nil { - return GroupChat{}, err + return nil, err } // Start group request and message retrieval workers err = client.api.AddService(m.StartProcesses) if err != nil { - return GroupChat{}, err + return nil, err } - return GroupChat{m}, nil + return &GroupChat{m}, nil } // MakeGroup creates a new group and sends a group request to all members in the // group. The ID of the new group, the rounds the requests were sent on, and the // status of the send are contained in NewGroupReport. -func (g GroupChat) MakeGroup(membership IdList, name, message []byte) (NewGroupReport, error) { +func (g *GroupChat) MakeGroup(membership *IdList, name, message []byte) ( + *NewGroupReport, error) { grp, rounds, status, err := g.m.MakeGroup(membership.list, name, message) - return NewGroupReport{Group{grp}, rounds, status}, err + return &NewGroupReport{&Group{grp}, rounds, status}, err } // ResendRequest resends a group request to all members in the group. The rounds // they were sent on and the status of the send are contained in NewGroupReport. -func (g GroupChat) ResendRequest(groupIdBytes []byte) (NewGroupReport, error) { +func (g *GroupChat) ResendRequest(groupIdBytes []byte) (*NewGroupReport, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return NewGroupReport{}, + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } rounds, status, err := g.m.ResendRequest(groupID) - return NewGroupReport{Group{}, rounds, status}, nil + return &NewGroupReport{&Group{}, rounds, status}, nil } // JoinGroup allows a user to join a group when they receive a request. The // caller must pass in the serialized bytes of a Group. -func (g GroupChat) JoinGroup(serializedGroupData []byte) error { +func (g *GroupChat) JoinGroup(serializedGroupData []byte) error { grp, err := gs.DeserializeGroup(serializedGroupData) if err != nil { return err @@ -91,7 +94,7 @@ func (g GroupChat) JoinGroup(serializedGroupData []byte) error { } // LeaveGroup deletes a group so a user no longer has access. -func (g GroupChat) LeaveGroup(groupIdBytes []byte) error { +func (g *GroupChat) LeaveGroup(groupIdBytes []byte) error { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { return errors.Errorf("Failed to unmarshal group ID: %+v", err) @@ -102,61 +105,65 @@ func (g GroupChat) LeaveGroup(groupIdBytes []byte) error { // Send sends the message to the specified group. Returns the round the messages // were sent on. -func (g GroupChat) Send(groupIdBytes, message []byte) (int64, error) { +func (g *GroupChat) Send(groupIdBytes, message []byte) (*GroupSendReport, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return 0, errors.Errorf("Failed to unmarshal group ID: %+v", err) + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } - round, err := g.m.Send(groupID, message) - return int64(round), err + round, timestamp, err := g.m.Send(groupID, message) + return &GroupSendReport{round, timestamp}, err } // GetGroups returns an IdList containing a list of group IDs that the user is a // part of. -func (g GroupChat) GetGroups() IdList { - return IdList{g.m.GetGroups()} +func (g *GroupChat) GetGroups() *IdList { + return &IdList{g.m.GetGroups()} } // GetGroup returns the group with the group ID. If no group exists, then the // error "failed to find group" is returned. -func (g GroupChat) GetGroup(groupIdBytes []byte) (Group, error) { +func (g *GroupChat) GetGroup(groupIdBytes []byte) (*Group, error) { groupID, err := id.Unmarshal(groupIdBytes) if err != nil { - return Group{}, errors.Errorf("Failed to unmarshal group ID: %+v", err) + return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err) } grp, exists := g.m.GetGroup(groupID) if !exists { - return Group{}, errors.New("failed to find group") + return nil, errors.New("failed to find group") } - return Group{grp}, nil + return &Group{grp}, nil } // NumGroups returns the number of groups the user is a part of. -func (g GroupChat) NumGroups() int { +func (g *GroupChat) NumGroups() int { return g.m.NumGroups() } +//// +// NewGroupReport Structure +//// + // NewGroupReport is returned when creating a new group and contains the ID of // the group, a list of rounds that the group requests were sent on, and the // status of the send. type NewGroupReport struct { - group Group + group *Group rounds []id.Round status gc.RequestStatus } // GetGroup returns the Group. -func (ngr NewGroupReport) GetGroup() Group { +func (ngr *NewGroupReport) GetGroup() *Group { return ngr.group } // GetRoundList returns the RoundList containing a list of rounds requests were // sent on. -func (ngr NewGroupReport) GetRoundList() RoundList { - return RoundList{ngr.rounds} +func (ngr *NewGroupReport) GetRoundList() *RoundList { + return &RoundList{ngr.rounds} } // GetStatus returns the status of the requests sent when creating a new group. @@ -164,10 +171,41 @@ func (ngr NewGroupReport) GetRoundList() RoundList { // 1 all requests failed to send // 2 some request failed and some succeeded // 3, all requests sent successfully -func (ngr NewGroupReport) GetStatus() int { +func (ngr *NewGroupReport) GetStatus() int { return int(ngr.status) } +//// +// NewGroupReport Structure +//// + +// GroupSendReport is returned when sending a group message. It contains the +// round ID sent on and the timestamp of the send. +type GroupSendReport struct { + roundID id.Round + timestamp time.Time +} + +// GetRoundID returns the ID of the round that the send occurred on. +func (gsr *GroupSendReport) GetRoundID() int64 { + return int64(gsr.roundID) +} + +// GetTimestampNano returns the timestamp of the send in nanoseconds. +func (gsr *GroupSendReport) GetTimestampNano() int64 { + return gsr.timestamp.UnixNano() +} + +// GetTimestampMS returns the timestamp of the send in milliseconds. +func (gsr *GroupSendReport) GetTimestampMS() int64 { + ts := uint64(gsr.timestamp.UnixNano()) / uint64(time.Millisecond) + + // TODO: remove the print below once debugging is done. + jww.DEBUG.Printf("Sent group message timestamp: %s", gsr.timestamp) + jww.DEBUG.Printf("Sent group message timestamp MS: %d", ts) + return int64(ts) +} + //// // Group Structure //// @@ -179,24 +217,42 @@ type Group struct { } // GetName returns the name set by the user for the group. -func (g Group) GetName() []byte { +func (g *Group) GetName() []byte { return g.g.Name } // GetID return the 33-byte unique group ID. -func (g Group) GetID() []byte { +func (g *Group) GetID() []byte { return g.g.ID.Bytes() } +// GetInitMessage returns initial message sent with the group request. +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. -func (g Group) GetMembership() GroupMembership { - return GroupMembership{g.g.Members} +func (g *Group) GetMembership() *GroupMembership { + return &GroupMembership{g.g.Members} } // Serialize serializes the Group. -func (g Group) Serialize() []byte { +func (g *Group) Serialize() []byte { return g.g.Serialize() } @@ -211,18 +267,18 @@ type GroupMembership struct { } // Len returns the number of members in the group membership. -func (gm GroupMembership) Len() int { - return gm.Len() +func (gm *GroupMembership) Len() int { + return len(gm.m) } // Get returns the member at the index. The member at index 0 is always the // group leader. An error is returned if the index is out of range. -func (gm GroupMembership) Get(i int) (GroupMember, error) { - if i < 0 || i > gm.Len() { - return GroupMember{}, errors.Errorf("ID list index must be between %d "+ +func (gm *GroupMembership) Get(i int) (*GroupMember, error) { + if i < 0 || i >= gm.Len() { + return nil, errors.Errorf("ID list index must be between %d "+ "and the last element %d.", 0, gm.Len()) } - return GroupMember{gm.m[i]}, nil + return &GroupMember{gm.m[i]}, nil } //// @@ -240,7 +296,7 @@ func (gm GroupMember) GetID() []byte { // GetDhKey returns the byte representation of the public Diffie–Hellman key of // the member. -func (gm GroupMember) GetDhKey() []byte { +func (gm *GroupMember) GetDhKey() []byte { return gm.DhKey.Bytes() } @@ -255,47 +311,67 @@ type GroupMessageReceive struct { } // GetGroupID returns the 33-byte group ID. -func (gmr GroupMessageReceive) GetGroupID() []byte { +func (gmr *GroupMessageReceive) GetGroupID() []byte { return gmr.GroupID.Bytes() } // GetMessageID returns the message ID. -func (gmr GroupMessageReceive) GetMessageID() []byte { +func (gmr *GroupMessageReceive) GetMessageID() []byte { return gmr.ID.Bytes() } // GetPayload returns the message payload. -func (gmr GroupMessageReceive) GetPayload() []byte { +func (gmr *GroupMessageReceive) GetPayload() []byte { return gmr.Payload } // GetSenderID returns the 33-byte user ID of the sender. -func (gmr GroupMessageReceive) GetSenderID() []byte { +func (gmr *GroupMessageReceive) GetSenderID() []byte { return gmr.SenderID.Bytes() } // GetRecipientID returns the 33-byte user ID of the recipient. -func (gmr GroupMessageReceive) GetRecipientID() []byte { +func (gmr *GroupMessageReceive) GetRecipientID() []byte { return gmr.RecipientID.Bytes() } // GetEphemeralID returns the ephemeral ID of the recipient. -func (gmr GroupMessageReceive) GetEphemeralID() int64 { +func (gmr *GroupMessageReceive) GetEphemeralID() int64 { return gmr.EphemeralID.Int64() } // GetTimestampNano returns the message timestamp in nanoseconds. -func (gmr GroupMessageReceive) GetTimestampNano() int64 { +func (gmr *GroupMessageReceive) GetTimestampNano() int64 { return gmr.Timestamp.UnixNano() } +// GetTimestampMS returns the message timestamp in milliseconds. +func (gmr *GroupMessageReceive) GetTimestampMS() int64 { + + ts := uint64(gmr.Timestamp.UnixNano()) / uint64(time.Millisecond) + // TODO: remove the print below once debugging is done. + jww.DEBUG.Printf("Received group message timestamp: %s", gmr.Timestamp) + jww.DEBUG.Printf("Received group message timestamp MS: %d", ts) + return int64(ts) +} + // GetRoundID returns the ID of the round the message was sent on. -func (gmr GroupMessageReceive) GetRoundID() int64 { +func (gmr *GroupMessageReceive) GetRoundID() int64 { return int64(gmr.RoundID) } // GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the // message was sent on. -func (gmr GroupMessageReceive) GetRoundTimestampNano() int64 { +func (gmr *GroupMessageReceive) GetRoundTimestampNano() int64 { return gmr.RoundTimestamp.UnixNano() } + +// GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the +// message was sent on. +func (gmr *GroupMessageReceive) GetRoundTimestampMS() int64 { + ts := uint64(gmr.RoundTimestamp.UnixNano()) / uint64(time.Millisecond) + // TODO: remove the print below once debugging is done. + jww.DEBUG.Printf("Received group message round timestamp: %s", gmr.RoundTimestamp) + jww.DEBUG.Printf("Received group message round timestamp MS: %d", ts) + return int64(ts) +} diff --git a/bindings/list.go b/bindings/list.go index a97df24f299d994380d46de8ae9971ad9133c1e7..1331c378e2b2f31c457ef2f3d082c51e186f2fbb 100644 --- a/bindings/list.go +++ b/bindings/list.go @@ -123,17 +123,17 @@ type IdList struct { } // MakeIdList creates a new empty IdList. -func MakeIdList() IdList { - return IdList{[]*id.ID{}} +func MakeIdList() *IdList { + return &IdList{[]*id.ID{}} } // Len returns the number of IDs in the list. -func (idl IdList) Len() int { +func (idl *IdList) Len() int { return len(idl.list) } // Add appends the ID bytes to the end of the list. -func (idl IdList) Add(idBytes []byte) error { +func (idl *IdList) Add(idBytes []byte) error { newID, err := id.Unmarshal(idBytes) if err != nil { return err @@ -145,7 +145,7 @@ func (idl IdList) Add(idBytes []byte) error { // Get returns the ID at the index. An error is returned if the index is out of // range. -func (idl IdList) Get(i int) ([]byte, error) { +func (idl *IdList) Get(i int) ([]byte, error) { if i < 0 || i > len(idl.list) { return nil, errors.Errorf("ID list index must be between %d and the "+ "last element %d.", 0, len(idl.list)) 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/group.go b/cmd/group.go index f1e4508a72e2ce6c0c29b836e67e7b0bef8d91d9..b973fb5c7f2b7addcb12a26c2a59b7b4c1524c22 100644 --- a/cmd/group.go +++ b/cmd/group.go @@ -154,7 +154,8 @@ func createGroup(name, msg []byte, filePath string, gm *groupChat.Manager) { // Integration grabs the group ID from this line jww.INFO.Printf("NewGroupID: b64:%s", grp.ID) - jww.INFO.Printf("Created Group: Requests:%s on rounds %#v, %v", status, rids, grp) + jww.INFO.Printf("Created Group: Requests:%s on rounds %#v, %v", + status, rids, grp) fmt.Printf("Created new group with name %q and message %q\n", grp.Name, grp.InitMessage) } @@ -168,13 +169,15 @@ func resendRequests(groupIdString string, gm *groupChat.Manager) { groupID, err) } - jww.INFO.Printf("Resending requests to group %s: %v, %s", groupID, rids, status) + jww.INFO.Printf("Resending requests to group %s: %v, %s", + groupID, rids, status) fmt.Println("Resending group requests to group.") } // joinGroup joins a group when a request is received on the group request // channel. -func joinGroup(reqChan chan groupStore.Group, timeout time.Duration, gm *groupChat.Manager) { +func joinGroup(reqChan chan groupStore.Group, timeout time.Duration, + gm *groupChat.Manager) { jww.INFO.Print("Waiting for group request to be received.") fmt.Println("Waiting for group request to be received.") @@ -215,18 +218,20 @@ func sendGroup(groupIdString string, msg []byte, gm *groupChat.Manager) { jww.INFO.Printf("Sending to group %s message %q", groupID, msg) - rid, err := gm.Send(groupID, msg) + rid, timestamp, err := gm.Send(groupID, msg) if err != nil { jww.FATAL.Panicf("Sending message to group %s: %+v", groupID, err) } - jww.INFO.Printf("Sent to group %s on round %d", groupID, rid) + jww.INFO.Printf("Sent to group %s on round %d at %s", + groupID, rid, timestamp) fmt.Printf("Sent message %q to group.\n", msg) } // messageWait waits for the given number of messages to be received on the // groupChat.MessageReceive channel. -func messageWait(numMessages uint, timeout time.Duration, recChan chan groupChat.MessageReceive) { +func messageWait(numMessages uint, timeout time.Duration, + recChan chan groupChat.MessageReceive) { jww.INFO.Printf("Waiting for %d group message(s) to be received.", numMessages) fmt.Printf("Waiting for %d group message(s) to be received.\n", numMessages) @@ -263,7 +268,8 @@ func showGroup(groupIdString string, gm *groupChat.Manager) { } jww.INFO.Printf("Show group %#v", grp) - fmt.Printf("Got group with name %q and message %q\n", grp.Name, grp.InitMessage) + fmt.Printf("Got group with name %q and message %q\n", + grp.Name, grp.InitMessage) } // ReadLines returns each line in a file as a string. @@ -272,7 +278,12 @@ func ReadLines(fileName string) []string { if err != nil { jww.FATAL.Panicf(err.Error()) } - defer file.Close() + defer func(file *os.File) { + err = file.Close() + if err != nil { + jww.FATAL.Panicf("Failed to close file: %+v", err) + } + }(file) var res []string diff --git a/cmd/root.go b/cmd/root.go index a88efa9fe5517b40e7a0f05beb51fe3ce5ef5dca..23416872ee7e210fbe6b886bee12788a17d0e1bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -194,7 +194,7 @@ var rootCmd = &cobra.Command{ go func() { //sendDelay := time.Duration(viper.GetUint("sendDelay")) for i := 0; i < sendCnt; i++ { - go func() { + go func(i int) { defer wg.Done() fmt.Printf("Sending to %s: %s\n", recipientID, msgBody) var roundIDs []id.Round @@ -225,9 +225,9 @@ var rootCmd = &cobra.Command{ } if err != nil { - jww.FATAL.Panicf("%+v", err) + jww.FATAL.Panicf("Message sending for send %d failed: %+v", i, err) } - }() + }(i) } }() @@ -236,14 +236,15 @@ var rootCmd = &cobra.Command{ expectedCnt := viper.GetUint("receiveCount") receiveCnt := uint(0) waitSecs := viper.GetUint("waitTimeout") - waitTimeout := time.Duration(waitSecs) + waitTimeout := time.Duration(waitSecs) * time.Second done := false for !done && expectedCnt != 0 { - timeoutTimer := time.NewTimer(waitTimeout * time.Second) + timeoutTimer := time.NewTimer(waitTimeout) select { case <-timeoutTimer.C: fmt.Println("Timed out!") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) done = true break case m := <-recvCh: @@ -253,10 +254,28 @@ var rootCmd = &cobra.Command{ receiveCnt++ if receiveCnt == expectedCnt { done = true + break } + } + } + + //wait an extra 5 seconds to make sure no messages were missed + done = false + timer := time.NewTimer(5 * time.Second) + for !done { + select { + case <-timer.C: + done = true break + case m := <-recvCh: + fmt.Printf("Message received: %s\n", string( + m.Payload)) + //fmt.Printf("%s", m.Timestamp) + receiveCnt++ } } + + jww.INFO.Printf("Received %d/%d Messages!", receiveCnt, expectedCnt) fmt.Printf("Received %d\n", receiveCnt) if roundsNotepad != nil { roundsNotepad.INFO.Printf("\n%s", client.GetNetworkInterface().GetVerboseRounds()) 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/manager.go b/dummy/manager.go new file mode 100644 index 0000000000000000000000000000000000000000..edf0b3ac3d1ece2e8fda7ab81376d9b162241a8d --- /dev/null +++ b/dummy/manager.go @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +// Package dummy allows for the sending of dummy messages to dummy recipients +// via SendCmix at randomly generated intervals. + +package dummy + +import ( + "gitlab.com/elixxir/client/api" + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/crypto/fastRNG" + "time" +) + +const ( + dummyTrafficStoppableName = "DummyTraffic" +) + +// Manager manages the sending of dummy messages. +type Manager struct { + // The maximum number of messages to send each send + maxNumMessages int + + // Average duration to wait between message sends + avgSendDelta time.Duration + + // Upper limit for random duration that modified avgSendDelta + randomRange time.Duration + + // Client interfaces + client *api.Client + store *storage.Session + net interfaces.NetworkManager + rng *fastRNG.StreamGenerator +} + +// NewManager creates a new dummy Manager with the specified average send delta +// and the range used for generating random durations. +func NewManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + client *api.Client) *Manager { + return newManager(maxNumMessages, avgSendDelta, randomRange, client, + client.GetStorage(), client.GetNetworkInterface(), client.GetRng()) +} + +// newManager builds a new dummy Manager from fields explicitly passed in. This +// function is a helper function for NewManager to make it easier to test. +func newManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + client *api.Client, store *storage.Session, net interfaces.NetworkManager, + rng *fastRNG.StreamGenerator) *Manager { + return &Manager{ + maxNumMessages: maxNumMessages, + avgSendDelta: avgSendDelta, + randomRange: randomRange, + client: client, + store: store, + net: net, + rng: rng, + } +} + +// StartDummyTraffic starts the process of sending dummy traffic. This function +// matches the api.Service type. +func (m *Manager) StartDummyTraffic() (stoppable.Stoppable, error) { + stop := stoppable.NewSingle(dummyTrafficStoppableName) + go m.sendThread(stop) + + return stop, nil +} diff --git a/dummy/manager_test.go b/dummy/manager_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7f8ec99d037dd14576951237abb6113518d89ab9 --- /dev/null +++ b/dummy/manager_test.go @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "reflect" + "testing" + "time" +) + +// Tests that newManager returns the expected Manager. +func Test_newManager(t *testing.T) { + expected := &Manager{ + maxNumMessages: 10, + avgSendDelta: time.Minute, + randomRange: time.Second, + } + + received := newManager(expected.maxNumMessages, expected.avgSendDelta, + expected.randomRange, nil, nil, nil, nil) + + if !reflect.DeepEqual(expected, received) { + t.Errorf("New manager does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } +} + +// Tests that Manager.StartDummyTraffic sends dummy messages and that it stops +// when the stoppable is closed. +func TestManager_StartDummyTraffic(t *testing.T) { + m := newTestManager(10, 50*time.Millisecond, 10*time.Millisecond, false, t) + + stop, err := m.StartDummyTraffic() + if err != nil { + t.Errorf("StartDummyTraffic returned an error: %+v", err) + } + + msgChan := make(chan bool) + go func() { + for m.net.(*testNetworkManager).GetMsgListLen() == 0 { + time.Sleep(5 * time.Millisecond) + } + msgChan <- true + }() + + var numReceived int + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + numReceived += m.net.(*testNetworkManager).GetMsgListLen() + } + + err = stop.Close() + if err != nil { + t.Errorf("Failed to close stoppable: %+v", err) + } + + time.Sleep(10 * time.Millisecond) + if !stop.IsStopped() { + t.Error("Stoppable never stopped.") + } + + msgChan = make(chan bool) + go func() { + for m.net.(*testNetworkManager).GetMsgListLen() == numReceived { + time.Sleep(5 * time.Millisecond) + } + msgChan <- true + }() + + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + + case <-msgChan: + t.Error("Received new messages after stoppable was stopped.") + } +} diff --git a/dummy/random.go b/dummy/random.go new file mode 100644 index 0000000000000000000000000000000000000000..2327ddf6c7d9b978ecf6e07e34f53b733155c87e --- /dev/null +++ b/dummy/random.go @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "encoding/binary" + "github.com/pkg/errors" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/csprng" + "time" +) // Error messages. +const ( + payloadSizeRngErr = "failed to generate random payload size: %+v" +) + +// intRng returns, as an int, a non-negative, non-zero random number in [1, n) +// from the csprng.Source. +func intRng(n int, rng csprng.Source) (int, error) { + v, err := csprng.Generate(8, rng) + if err != nil { + return 0, err + } + + return int(binary.LittleEndian.Uint64(v)%uint64(n-1)) + 1, nil +} + +// durationRng returns a duration that is the base duration plus or minus a +// random duration of max randomRange. +func durationRng(base, randomRange time.Duration, rng csprng.Source) ( + time.Duration, error) { + delta, err := intRng(int(2*randomRange), rng) + if err != nil { + return 0, err + } + + return base + randomRange - time.Duration(delta), nil +} + +// newRandomPayload generates a random payload of a random length. +func newRandomPayload(maxPayloadSize int, rng csprng.Source) ([]byte, error) { + // Generate random payload size + randomPayloadSize, err := intRng(maxPayloadSize, rng) + if err != nil { + return nil, errors.Errorf(payloadSizeRngErr, err) + } + + randomMsg, err := csprng.Generate(randomPayloadSize, rng) + if err != nil { + return nil, err + } + + return randomMsg, nil +} + +// newRandomFingerprint generates a random format.Fingerprint. +func newRandomFingerprint(rng csprng.Source) (format.Fingerprint, error) { + fingerprintBytes, err := csprng.Generate(format.KeyFPLen, rng) + if err != nil { + return format.Fingerprint{}, err + } + + // Create new fingerprint from bytes + fingerprint := format.NewFingerprint(fingerprintBytes) + + // Set the first bit to be 0 to comply with the cMix group + fingerprint[0] &= 0x7F + + return fingerprint, nil +} + +// newRandomMAC generates a random MAC. +func newRandomMAC(rng csprng.Source) ([]byte, error) { + mac, err := csprng.Generate(format.MacLen, rng) + if err != nil { + return nil, err + } + + // Set the first bit to be 0 to comply with the cMix group + mac[0] &= 0x7F + + return mac, nil +} diff --git a/dummy/random_test.go b/dummy/random_test.go new file mode 100644 index 0000000000000000000000000000000000000000..661986a0416993e211209d009a023c451dd3ff60 --- /dev/null +++ b/dummy/random_test.go @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "encoding/base64" + "testing" + "time" +) + +// Consistency test: tests that intRng returns the expected int when using a +// PRNG and that the result is not larger than the max. +func Test_intRng_Consistency(t *testing.T) { + expectedInts := []int{15, 1, 35, 13, 42, 52, 57, 3, 48} + + prng := NewPrng(42) + max := 64 + + for i, expected := range expectedInts { + v, err := intRng(max, prng) + if err != nil { + t.Errorf("intRng returned an error (%d): %+v", i, err) + } + + if v != expected { + t.Errorf("New int #%d does not match expected."+ + "\nexpected: %d\nreceived: %d", i, expected, v) + } + + // Ensure that the int is in range + if v > max || v < 1 { + t.Errorf("Int #%d not within range."+ + "\nexpected: %d < d < %d\nreceived: %d", i, 0, max, v) + } + } +} + +// Consistency test: tests that durationRng returns the expected int when using +// a PRNG and that the result is within the allowed range. +func Test_durationRng_Consistency(t *testing.T) { + expectedDurations := []time.Duration{ + 61460632462, 69300060600, 46066982720, 68493307162, 45820762465, + 56472560211, 68610237306, 45503877311, 63543617747, + } + + prng := NewPrng(42) + base, randomRange := time.Minute, 15*time.Second + + for i, expected := range expectedDurations { + v, err := durationRng(base, randomRange, prng) + if err != nil { + t.Errorf("durationRng returned an error (%d): %+v", i, err) + } + + if v != expected { + t.Errorf("New duration #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, v) + } + + // Ensure that the duration is within range + if v > base+randomRange || v < base-randomRange { + t.Errorf("Duration #%d is not in range."+ + "\nexpected: %s < d < %s\nreceived: %s", i, base-randomRange, + base+randomRange, v) + } + } +} + +// Consistency test: tests that newRandomPayload returns the expected payload +// when using a PRNG and that the result is not larger than the max payload. +func Test_newRandomPayload_Consistency(t *testing.T) { + expectedPayloads := []string{ + "l7ufS7Ry6J9bFITyUgnJ", + "Ut/Xm012Qpthegyfnw07pVsMwNYUTIiFNQ==", + "CD9h", + "GSnh", + "joE=", + "uoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44bC6+uiBuCpw==", + "qkNGWnhiBhaXiu0M48bE8657w+BJW1cS/v2+DBAoh+EA2s0tiF9pLLYH2gChHBxwcec=", + "suEpcF4nPwXJIyaCjisFbg==", + "R/3zREEO1MEWAj+o41drb+0n/4l0usDK/ZrQVpKxNhnnOJZN/ceejVNDc2Yc/WbXTw==", + "bkt1IQ==", + } + + prng := NewPrng(42) + maxPayloadSize := 64 + + for i, expected := range expectedPayloads { + payload, err := newRandomPayload(maxPayloadSize, prng) + if err != nil { + t.Errorf("newRandomPayload returned an error (%d): %+v", i, err) + } + + payloadString := base64.StdEncoding.EncodeToString(payload) + + if payloadString != expected { + t.Errorf("New payload #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, payloadString) + } + + // Ensure that the payload is not larger than the max size + if len(payload) > maxPayloadSize { + t.Errorf("Length of payload #%d longer than max allowed."+ + "\nexpected: <%d\nreceived: %d", i, maxPayloadSize, len(payload)) + } + } +} + +// Consistency test: tests that newRandomFingerprint returns the expected +// fingerprints when using a PRNG. Also tests that the first bit is zero. +func Test_newRandomFingerprint_Consistency(t *testing.T) { + expectedFingerprints := []string{ + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=", + "X9ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hBrL4HCbB1g=", + "CD9h03W8ArQd9PkZKeGP2p5vguVOdI6B555LvW/jTNw=", + "OoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44=", + "GwuvrogbgqdREIpC7TyQPKpDRlp4YgYWl4rtDOPGxPM=", + "LnvD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHA=", + "ceeWotwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGE=", + "SYlH/fNEQQ7UwRYCP6jjV2tv7Sf/iXS6wMr9mtBWkrE=", + "NhnnOJZN/ceejVNDc2Yc/WbXT+weG4lJGrcjbkt1IWI=", + "EM8r60LDyicyhWDxqsBnzqbov0bUqytGgEAsX7KCDog=", + } + + prng := NewPrng(42) + + for i, expected := range expectedFingerprints { + fp, err := newRandomFingerprint(prng) + if err != nil { + t.Errorf("newRandomFingerprint returned an error (%d): %+v", i, err) + } + + if fp.String() != expected { + t.Errorf("New fingerprint #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, fp) + } + + // Ensure that the first bit is zero + if fp[0]>>7 != 0 { + t.Errorf("First bit of fingerprint #%d is not 0."+ + "\nexpected: %d\nreceived: %d", i, 0, fp[0]>>7) + } + } +} + +// Consistency test: tests that newRandomMAC returns the expected MAC when using +// a PRNG. Also tests that the first bit is zero. +func Test_newRandomMAC_Consistency(t *testing.T) { + expectedMACs := []string{ + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVI=", + "X9ebTXZCm2F6DJ+fDTulWwzA1hRMiIU1hBrL4HCbB1g=", + "CD9h03W8ArQd9PkZKeGP2p5vguVOdI6B555LvW/jTNw=", + "OoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44=", + "GwuvrogbgqdREIpC7TyQPKpDRlp4YgYWl4rtDOPGxPM=", + "LnvD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHA=", + "ceeWotwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGE=", + "SYlH/fNEQQ7UwRYCP6jjV2tv7Sf/iXS6wMr9mtBWkrE=", + "NhnnOJZN/ceejVNDc2Yc/WbXT+weG4lJGrcjbkt1IWI=", + "EM8r60LDyicyhWDxqsBnzqbov0bUqytGgEAsX7KCDog=", + } + + prng := NewPrng(42) + + for i, expected := range expectedMACs { + mac, err := newRandomMAC(prng) + if err != nil { + t.Errorf("newRandomMAC returned an error (%d): %+v", i, err) + } + + macString := base64.StdEncoding.EncodeToString(mac) + + if macString != expected { + t.Errorf("New MAC #%d does not match expected."+ + "\nexpected: %s\nreceived: %s", i, expected, macString) + } + + // Ensure that the first bit is zero + if mac[0]>>7 != 0 { + t.Errorf("First bit of MAC #%d is not 0."+ + "\nexpected: %d\nreceived: %d", i, 0, mac[0]>>7) + } + } +} diff --git a/dummy/send.go b/dummy/send.go new file mode 100644 index 0000000000000000000000000000000000000000..f37f79927402b86f59606d901c0f2d393ff41b05 --- /dev/null +++ b/dummy/send.go @@ -0,0 +1,173 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "sync" + "sync/atomic" + "time" +) + +// Error messages. +const ( + numMsgsRngErr = "failed to generate random number of messages to send: %+v" + payloadRngErr = "failed to generate random payload: %+v" + recipientRngErr = "failed to generate random recipient: %+v" + fingerprintRngErr = "failed to generate random fingerprint: %+v" + macRngErr = "failed to generate random MAC: %+v" +) + +// sendThread is a thread that sends the dummy messages at random intervals. +func (m *Manager) sendThread(stop *stoppable.Single) { + jww.DEBUG.Print("Starting dummy traffic sending thread.") + + timer := m.randomTimer() + + for { + select { + case <-stop.Quit(): + jww.DEBUG.Print("Stopping dummy traffic sending thread: stoppable " + + "triggered") + stop.ToStopped() + return + case <-timer.C: + timer = m.randomTimer() + + // Get list of random messages and recipients + rng := m.rng.GetStream() + msgs, err := m.newRandomMessages(rng) + if err != nil { + jww.FATAL.Panicf("Failed to generate dummy messages: %+v", err) + } + rng.Close() + + err = m.sendMessages(msgs) + if err != nil { + jww.FATAL.Panicf("Failed to send dummy messages: %+v", err) + } + } + } +} + +// sendMessages generates and sends random messages. +func (m *Manager) sendMessages(msgs map[id.ID]format.Message) error { + var sent, i int64 + var wg sync.WaitGroup + + for recipient, msg := range msgs { + wg.Add(1) + + go func(i int64, recipient id.ID, msg format.Message) { + //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) + } else { + atomic.AddInt64(&sent, 1) + } + + wg.Done() + }(i, recipient, msg) + + i++ + } + + wg.Wait() + + jww.INFO.Printf("Sent %d/%d dummy messages.", sent, len(msgs)) + + return nil +} + +// newRandomMessages returns a map of a random recipients and random messages of +// a randomly generated length in [1, Manager.maxNumMessages]. +func (m *Manager) newRandomMessages(rng csprng.Source) ( + map[id.ID]format.Message, error) { + numMessages, err := intRng(m.maxNumMessages+1, rng) + if err != nil { + return nil, errors.Errorf(numMsgsRngErr, err) + } + + msgs := make(map[id.ID]format.Message, numMessages) + + for i := 0; i < numMessages; i++ { + // Generate random recipient + recipient, err := id.NewRandomID(rng, id.User) + if err != nil { + return nil, errors.Errorf(recipientRngErr, err) + } + + msgs[*recipient], err = m.newRandomCmixMessage(rng) + if err != nil { + return nil, err + } + } + + return msgs, nil +} + +// newRandomCmixMessage returns a new cMix message filled with a randomly +// generated payload, fingerprint, and MAC. +func (m *Manager) newRandomCmixMessage(rng csprng.Source) (format.Message, error) { + // Create new empty cMix message + cMixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen()) + + // Generate random message + randomMsg, err := newRandomPayload(cMixMsg.ContentsSize(), rng) + if err != nil { + return format.Message{}, errors.Errorf(payloadRngErr, err) + } + + // Generate random fingerprint + fingerprint, err := newRandomFingerprint(rng) + if err != nil { + return format.Message{}, errors.Errorf(fingerprintRngErr, err) + } + + // Generate random MAC + mac, err := newRandomMAC(rng) + if err != nil { + return format.Message{}, errors.Errorf(macRngErr, err) + } + + // Set contents, fingerprint, and MAC, of the cMix message + cMixMsg.SetContents(randomMsg) + cMixMsg.SetKeyFP(fingerprint) + cMixMsg.SetMac(mac) + + return cMixMsg, nil +} + +// randomTimer generates a timer that will trigger after a random duration. +func (m *Manager) randomTimer() *time.Timer { + rng := m.rng.GetStream() + + duration, err := durationRng(m.avgSendDelta, m.randomRange, rng) + if err != nil { + jww.FATAL.Panicf("Failed to generate random duration to wait to send "+ + "dummy messages: %+v", err) + } + + return time.NewTimer(duration) +} diff --git a/dummy/send_test.go b/dummy/send_test.go new file mode 100644 index 0000000000000000000000000000000000000000..acf08cb558ba02fbf711537ce67a696afa455836 --- /dev/null +++ b/dummy/send_test.go @@ -0,0 +1,169 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "bytes" + "encoding/base64" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" + "time" +) + +// Tests that Manager.sendThread sends multiple sets of messages. +func TestManager_sendThread(t *testing.T) { + m := newTestManager(10, 50*time.Millisecond, 10*time.Millisecond, false, t) + + stop := stoppable.NewSingle("sendThreadTest") + go m.sendThread(stop) + + msgChan := make(chan bool, 10) + go func() { + var numReceived int + for i := 0; i < 2; i++ { + for m.net.(*testNetworkManager).GetMsgListLen() == numReceived { + time.Sleep(5 * time.Millisecond) + } + numReceived = m.net.(*testNetworkManager).GetMsgListLen() + msgChan <- true + } + }() + + var numReceived int + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + numReceived += m.net.(*testNetworkManager).GetMsgListLen() + } + + select { + case <-time.NewTimer(3 * m.avgSendDelta).C: + t.Errorf("Timed out after %s waiting for messages to be sent.", + 3*m.avgSendDelta) + case <-msgChan: + if m.net.(*testNetworkManager).GetMsgListLen() <= numReceived { + t.Errorf("Failed to receive second send."+ + "\nmessages on last receive: %d\nmessages on this receive: %d", + numReceived, m.net.(*testNetworkManager).GetMsgListLen()) + } + } + + err := stop.Close() + if err != nil { + t.Errorf("Failed to close stoppable: %+v", err) + } + + time.Sleep(10 * time.Millisecond) + if !stop.IsStopped() { + t.Error("Stoppable never stopped.") + } +} + +// Tests that Manager.sendMessages sends all the messages with the correct +// recipient. +func TestManager_sendMessages(t *testing.T) { + m := newTestManager(100, 0, 0, false, t) + prng := NewPrng(42) + + // Generate map of recipients and messages + msgs := make(map[id.ID]format.Message, m.maxNumMessages) + for i := 0; i < m.maxNumMessages; i++ { + recipient, err := id.NewRandomID(prng, id.User) + if err != nil { + t.Errorf("Failed to generate random recipient ID (%d): %+v", i, err) + } + + msg, err := m.newRandomCmixMessage(prng) + if err != nil { + t.Errorf("Failed to generate random cMix message (%d): %+v", i, err) + } + + msgs[*recipient] = msg + } + + // Send the messages + err := m.sendMessages(msgs) + if err != nil { + t.Errorf("sendMessages returned an error: %+v", err) + } + + // Get sent messages + receivedMsgs := m.net.(*testNetworkManager).GetMsgList() + + // Test that all messages were received + if len(receivedMsgs) != len(msgs) { + t.Errorf("Failed to received all sent messages."+ + "\nexpected: %d\nreceived: %d", len(msgs), len(receivedMsgs)) + } + + // Test that all messages were received for the correct recipient + for recipient, msg := range msgs { + receivedMsg, exists := receivedMsgs[recipient] + if !exists { + t.Errorf("Failed to receive message from %s: %+v", &recipient, msg) + } else if !reflect.DeepEqual(msg, receivedMsg) { + t.Errorf("Received unexpected message for recipient %s."+ + "\nexpected: %+v\nreceived: %+v", &recipient, msg, receivedMsg) + } + } +} + +// Tests that Manager.newRandomMessages creates a non-empty map of messages and +// that each message is unique. +func TestManager_newRandomMessages(t *testing.T) { + m := newTestManager(10, 0, 0, false, t) + prng := NewPrng(42) + + msgMap, err := m.newRandomMessages(prng) + if err != nil { + t.Errorf("newRandomMessages returned an error: %+v", err) + } + + if len(msgMap) == 0 { + t.Error("Message map is empty.") + } + + marshalledMsgs := make(map[string]format.Message, len(msgMap)) + for _, msg := range msgMap { + msgString := base64.StdEncoding.EncodeToString(msg.Marshal()) + if _, exists := marshalledMsgs[msgString]; exists { + t.Errorf("Message not unique.") + } else { + marshalledMsgs[msgString] = msg + } + } +} + +// Tests that Manager.newRandomCmixMessage generates a cMix message with +// populated contents, fingerprint, and MAC. +func TestManager_newRandomCmixMessage(t *testing.T) { + m := newTestManager(0, 0, 0, false, t) + prng := NewPrng(42) + + cMixMsg, err := m.newRandomCmixMessage(prng) + if err != nil { + t.Errorf("newRandomCmixMessage returned an error: %+v", err) + } + + if bytes.Equal(cMixMsg.GetContents(), make([]byte, len(cMixMsg.GetContents()))) { + t.Error("cMix message contents not set.") + } + + if cMixMsg.GetKeyFP() == (format.Fingerprint{}) { + t.Error("cMix message fingerprint not set.") + } + + if bytes.Equal(cMixMsg.GetMac(), make([]byte, format.MacLen)) { + t.Error("cMix message MAC not set.") + } +} diff --git a/dummy/utils_test.go b/dummy/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7312effd4d1d7b9d138e467d764f08fd5ddbd401 --- /dev/null +++ b/dummy/utils_test.go @@ -0,0 +1,208 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package dummy + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/network/gateway" + "gitlab.com/elixxir/client/stoppable" + "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/comms/network" + "gitlab.com/elixxir/crypto/e2e" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/ndf" + "io" + "math/rand" + "sync" + "testing" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// PRNG // +//////////////////////////////////////////////////////////////////////////////// + +// Prng is a PRNG that satisfies the csprng.Source interface. +type Prng struct{ prng io.Reader } + +func NewPrng(seed int64) csprng.Source { return &Prng{rand.New(rand.NewSource(seed))} } +func (s *Prng) Read(b []byte) (int, error) { return s.prng.Read(b) } +func (s *Prng) SetSeed([]byte) error { return nil } + +//////////////////////////////////////////////////////////////////////////////// +// Test Managers // +//////////////////////////////////////////////////////////////////////////////// + +// newTestManager creates a new Manager that has groups stored for testing. One +// of the groups in the list is also returned. +func newTestManager(maxNumMessages int, avgSendDelta, randomRange time.Duration, + sendErr bool, t *testing.T) *Manager { + m := &Manager{ + maxNumMessages: maxNumMessages, + avgSendDelta: avgSendDelta, + randomRange: randomRange, + store: storage.InitTestingSession(t), + net: newTestNetworkManager(sendErr, t), + rng: fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG), + } + + return m +} + +//////////////////////////////////////////////////////////////////////////////// +// Test Network Manager // +//////////////////////////////////////////////////////////////////////////////// + +// testNetworkManager is a test implementation of NetworkManager interface. +type testNetworkManager struct { + instance *network.Instance + messages map[id.ID]format.Message + sendErr bool + sync.RWMutex +} + +func newTestNetworkManager(sendErr bool, t *testing.T) interfaces.NetworkManager { + instanceComms := &connect.ProtoComms{ + Manager: connect.NewManagerTesting(t), + } + + thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), + getNDF(), nil, nil, t) + if err != nil { + t.Fatalf("Failed to create new test instance: %v", err) + } + + return &testNetworkManager{ + instance: thisInstance, + messages: make(map[id.ID]format.Message), + sendErr: sendErr, + } +} + +func (tnm *testNetworkManager) GetMsgListLen() int { + tnm.RLock() + defer tnm.RUnlock() + return len(tnm.messages) +} + +func (tnm *testNetworkManager) GetMsgList() map[id.ID]format.Message { + tnm.RLock() + defer tnm.RUnlock() + return tnm.messages +} + +func (tnm *testNetworkManager) GetMsg(recipient id.ID) format.Message { + tnm.RLock() + defer tnm.RUnlock() + return tnm.messages[recipient] +} + +func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Single) ( + []id.Round, e2e.MessageID, time.Time, error) { + return nil, e2e.MessageID{}, time.Time{}, nil +} + +func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) { + return []id.Round{}, nil +} + +func (tnm *testNetworkManager) SendCMIX(message format.Message, + recipient *id.ID, _ params.CMIX) (id.Round, ephemeral.Id, error) { + tnm.Lock() + defer tnm.Unlock() + + if tnm.sendErr { + return 0, ephemeral.Id{}, errors.New("SendCMIX error") + } + + tnm.messages[*recipient] = message + + return 0, ephemeral.Id{}, nil +} + +func (tnm *testNetworkManager) SendManyCMIX(map[id.ID]format.Message, params.CMIX) ( + id.Round, []ephemeral.Id, error) { + return 0, nil, nil +} + +type dummyEventMgr struct{} + +func (d *dummyEventMgr) Report(int, string, string, string) {} +func (tnm *testNetworkManager) GetEventManager() interfaces.EventManager { + return &dummyEventMgr{} +} + +func (tnm *testNetworkManager) GetInstance() *network.Instance { return tnm.instance } +func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { return nil } +func (tnm *testNetworkManager) Follow(interfaces.ClientErrorReport) (stoppable.Stoppable, error) { + return nil, nil +} +func (tnm *testNetworkManager) CheckGarbledMessages() {} +func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 } +func (tnm *testNetworkManager) GetSender() *gateway.Sender { return nil } +func (tnm *testNetworkManager) GetAddressSize() uint8 { return 0 } +func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) { + return nil, nil +} +func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {} +func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter) {} +func (tnm *testNetworkManager) GetVerboseRounds() string { return "" } + +//////////////////////////////////////////////////////////////////////////////// +// NDF Primes // +//////////////////////////////////////////////////////////////////////////////// + +func getNDF() *ndf.NetworkDefinition { + return &ndf.NetworkDefinition{ + E2E: ndf.Group{ + Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" + + "8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" + + "D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" + + "75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" + + "6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" + + "4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" + + "6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" + + "448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" + + "198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" + + "DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" + + "631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" + + "3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" + + "19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" + + "5873847AEF49F66E43873", + Generator: "2", + }, + CMIX: ndf.Group{ + Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" + + "F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" + + "264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" + + "9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" + + "B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" + + "0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" + + "92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" + + "2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" + + "995FAD5AABBCFBE3EDA2741E375404AE25B", + Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" + + "9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" + + "1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" + + "8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" + + "C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" + + "5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" + + "59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" + + "2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" + + "B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + }, + } +} diff --git a/go.mod b/go.mod index 08c4114e07155d2c7465384a23d5095caffe385d..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.20211014164205-95915de2ac0d + 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 02eac1d245cf2b94154da84caf1db94ce8708c30..7f6eadf3204a34ab24a16072f09654993f1d480c 100644 --- a/go.sum +++ b/go.sum @@ -253,12 +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.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/group.go b/groupChat/group.go index 5d67d19f1e52f80cf1628f2deece083b4d8f4568..8878a62cdf750823bd4c886fbfbfdca1ac81c13d 100644 --- a/groupChat/group.go +++ b/groupChat/group.go @@ -22,6 +22,7 @@ package groupChat import ( gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/xx_network/primitives/id" + "time" ) // GroupChat is used to send and receive cMix messages to/from multiple users. @@ -49,8 +50,9 @@ type GroupChat interface { LeaveGroup(groupID *id.ID) error // Send sends a message to all GroupChat members using Client.SendManyCMIX. - // The send fails if the message is too long. - Send(groupID *id.ID, message []byte) (id.Round, error) + // The send fails if the message is too long. Returns the ID of the round + // sent on and the timestamp of the message send. + Send(groupID *id.ID, message []byte) (id.Round, time.Time, error) // GetGroups returns a list of all registered GroupChat IDs. GetGroups() []*id.ID diff --git a/groupChat/groupStore/dhKeyList.go b/groupChat/groupStore/dhKeyList.go index 50c405c426d7a27f2f99767208d2b2915a4f93b5..38b7b56bf3d88c6bed432e29b5a580c270a0285a 100644 --- a/groupChat/groupStore/dhKeyList.go +++ b/groupChat/groupStore/dhKeyList.go @@ -25,15 +25,17 @@ const ( dhKeyDecodeErr = "failed to decode member DH key: %+v" ) +// DhKeyList is a map of users to their DH key. type DhKeyList map[id.ID]*cyclic.Int -// GenerateDhKeyList generates the symmetric/DH key between the user and all -// group members. +// GenerateDhKeyList generates the DH key between the user and all group +// members. func GenerateDhKeyList(userID *id.ID, privKey *cyclic.Int, members group.Membership, grp *cyclic.Group) DhKeyList { 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) } @@ -86,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 98d0b489de808cf1ed5ca31818f4bc7f203fe878..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))) @@ -207,7 +230,8 @@ func DeserializeGroup(data []byte) (Group, error) { return g, err } -// groupStoreKey generates a unique key to save and load a Group to/from storage. +// groupStoreKey generates a unique key to save and load a Group to/from +// storage. func groupStoreKey(groupID *id.ID) string { return groupStorageKey + groupID.String() } @@ -220,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 @@ -228,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 ff855db08634dac15c33b918e14a3d394c90ef2b..2da1e2868991259142ec74cef049f9e9acd1035f 100644 --- a/groupChat/groupStore/group_test.go +++ b/groupChat/groupStore/group_test.go @@ -22,7 +22,8 @@ import ( func TestNewGroup(t *testing.T) { prng := rand.New(rand.NewSource(42)) membership := createMembership(prng, 10, t) - dkl := GenerateDhKeyList(membership[0].ID, randCycInt(prng), membership, getGroup()) + dkl := GenerateDhKeyList( + membership[0].ID, randCycInt(prng), membership, getGroup()) expectedGroup := Group{ Name: []byte(groupName), @@ -31,6 +32,7 @@ func TestNewGroup(t *testing.T) { IdPreimage: newIdPreimage(groupIdPreimage), KeyPreimage: newKeyPreimage(groupKeyPreimage), InitMessage: []byte(initMessage), + Created: created, Members: membership, DhKeys: dkl, } @@ -42,6 +44,7 @@ func TestNewGroup(t *testing.T) { newIdPreimage(groupIdPreimage), newKeyPreimage(groupKeyPreimage), []byte(initMessage), + expectedGroup.Created, membership, dkl, ) @@ -260,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."+ @@ -278,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/store.go b/groupChat/groupStore/store.go index 88e980603e323114b2a0c39a138f0e2977627053..f180249e7917b38704b0027b012f3a118262d9f6 100644 --- a/groupChat/groupStore/store.go +++ b/groupChat/groupStore/store.go @@ -40,7 +40,8 @@ const ( setUserPanic = "Store.SetUser is for testing only. Got %T" ) -// The maximum number of group chats that a user can be a part of at once. +// MaxGroupChats is the maximum number of group chats that a user can be a part +// of at once. const MaxGroupChats = 64 // Store stores the list of Groups that a user is a part of. @@ -265,7 +266,8 @@ func (s *Store) Get(groupID *id.ID) (Group, bool) { // GetByKeyFp returns the group with the matching key fingerprint and salt. // Returns false if no group is found. -func (s *Store) GetByKeyFp(keyFp format.Fingerprint, salt [group.SaltLen]byte) (Group, bool) { +func (s *Store) GetByKeyFp(keyFp format.Fingerprint, salt [group.SaltLen]byte) ( + Group, bool) { s.mux.RLock() defer s.mux.RUnlock() diff --git a/groupChat/groupStore/store_test.go b/groupChat/groupStore/store_test.go index 0e0917ff38ce33b83cbecbfafb4f960ea8c38447..0303446c99e2a4c5d36e51887a494d18f68810f8 100644 --- a/groupChat/groupStore/store_test.go +++ b/groupChat/groupStore/store_test.go @@ -58,7 +58,7 @@ func TestNewStore(t *testing.T) { groupIds = append(groupIds, grpId) } - // Check that stored group Id list is expected value + // Check that stored group ID list is expected value expectedData := serializeGroupIdList(store.list) obj, err := store.kv.Get(groupListStorageKey, groupListVersion) @@ -211,7 +211,7 @@ func Test_serializeGroupIdList_deserializeGroupIdList(t *testing.T) { data := serializeGroupIdList(testMap) newList := deserializeGroupIdList(data) - // Sort expected and received lists so they are in the same order + // Sort expected and received lists so that they are in the same order sort.Slice(expected, func(i, j int) bool { return bytes.Compare(expected[i].Bytes(), expected[j].Bytes()) == -1 }) @@ -405,7 +405,7 @@ func TestStore_GroupIDs(t *testing.T) { newList := store.GroupIDs() - // Sort expected and received lists so they are in the same order + // Sort expected and received lists so that they are in the same order sort.Slice(expected, func(i, j int) bool { return bytes.Compare(expected[i].Bytes(), expected[j].Bytes()) == -1 }) 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.go b/groupChat/internalFormat.go index 2502a9c8c29c9f940a93bebb1101c5d5a5ae8ef2..e8fd69df643856b49a217a7ae0602ce7e741e363 100644 --- a/groupChat/internalFormat.go +++ b/groupChat/internalFormat.go @@ -94,7 +94,7 @@ func (im internalMsg) SetTimestamp(t time.Time) { binary.LittleEndian.PutUint64(im.timestamp, uint64(t.UnixNano())) } -// GetSenderID returns the sender ID bytes as a id.ID. +// GetSenderID returns the sender ID bytes as an id.ID. func (im internalMsg) GetSenderID() (*id.ID, error) { return id.Unmarshal(im.senderID) } diff --git a/groupChat/internalFormat_test.go b/groupChat/internalFormat_test.go index 984d11b8f35ec44935eaea48b5bbd76eec80bdb1..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."+ @@ -198,7 +200,8 @@ func TestInternalMsg_String(t *testing.T) { } } -// Happy path: tests that String returns the expected string for a nil internalMsg. +// Happy path: tests that String returns the expected string for a nil +// internalMsg. func TestInternalMsg_String_NilInternalMessage(t *testing.T) { im := internalMsg{} diff --git a/groupChat/makeGroup.go b/groupChat/makeGroup.go index fa230fadcc99f61a4b4a44d0f62aa549ec9ec306..7cfe7dfd8326b1f221b9ef4a9bf16c8096879c3a 100644 --- a/groupChat/makeGroup.go +++ b/groupChat/makeGroup.go @@ -9,11 +9,15 @@ package groupChat 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" ) @@ -73,22 +77,39 @@ 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 { + g := gs.NewGroup( + 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) } + jww.DEBUG.Printf("Created new group %q with ID %s and %d members %s", + g.Name, g.ID, len(g.Members), g.Members) + // 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 } // buildMembership retrieves the contact object for each member ID and creates a // new membership from them. The caller is set as the leader. For a member to be // added, the group leader must have an authenticated channel with the member. -func (m Manager) buildMembership(members []*id.ID) (group.Membership, gs.DhKeyList, error) { +func (m Manager) buildMembership(members []*id.ID) (group.Membership, + gs.DhKeyList, error) { // Return an error if the membership list has too few or too many members if len(members) < group.MinParticipants { return nil, nil, diff --git a/groupChat/makeGroup_test.go b/groupChat/makeGroup_test.go index 004bf452664984c8ec8651442ab10a7a64d48fee..6c38cae0fcd61d2c21e750c050c713d55b6edd77 100644 --- a/groupChat/makeGroup_test.go +++ b/groupChat/makeGroup_test.go @@ -77,7 +77,8 @@ func TestManager_MakeGroup(t *testing.T) { func TestManager_MakeGroup_MaxMessageSizeError(t *testing.T) { prng := rand.New(rand.NewSource(42)) m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t) - expectedErr := fmt.Sprintf(maxInitMsgSizeErr, MaxInitMessageSize+1, MaxInitMessageSize) + expectedErr := fmt.Sprintf( + maxInitMsgSizeErr, MaxInitMessageSize+1, MaxInitMessageSize) _, _, status, err := m.MakeGroup(nil, nil, make([]byte, MaxInitMessageSize+1)) if err == nil || err.Error() != expectedErr { @@ -96,7 +97,8 @@ func TestManager_MakeGroup_MaxMessageSizeError(t *testing.T) { func TestManager_MakeGroup_MembershipSizeError(t *testing.T) { prng := rand.New(rand.NewSource(42)) m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t) - expectedErr := fmt.Sprintf(maxMembersErr, group.MaxParticipants+1, group.MaxParticipants) + expectedErr := fmt.Sprintf( + maxMembersErr, group.MaxParticipants+1, group.MaxParticipants) _, _, status, err := m.MakeGroup(make([]*id.ID, group.MaxParticipants+1), nil, []byte{}) @@ -153,7 +155,8 @@ func TestManager_buildMembership(t *testing.T) { func TestManager_buildMembership_MinParticipantsError(t *testing.T) { m, _ := newTestManager(rand.New(rand.NewSource(42)), t) memberIDs := make([]*id.ID, group.MinParticipants-1) - expectedErr := fmt.Sprintf(minMembersErr, len(memberIDs), group.MinParticipants) + expectedErr := fmt.Sprintf( + minMembersErr, len(memberIDs), group.MinParticipants) _, _, err := m.buildMembership(memberIDs) if err == nil || !strings.Contains(err.Error(), expectedErr) { @@ -167,7 +170,8 @@ func TestManager_buildMembership_MinParticipantsError(t *testing.T) { func TestManager_buildMembership_MaxParticipantsError(t *testing.T) { m, _ := newTestManager(rand.New(rand.NewSource(42)), t) memberIDs := make([]*id.ID, group.MaxParticipants+1) - expectedErr := fmt.Sprintf(maxMembersErr, len(memberIDs), group.MaxParticipants) + expectedErr := fmt.Sprintf( + maxMembersErr, len(memberIDs), group.MaxParticipants) _, _, err := m.buildMembership(memberIDs) if err == nil || !strings.Contains(err.Error(), expectedErr) { @@ -275,7 +279,8 @@ func TestRequestStatus_Message(t *testing.T) { // addPartners returns a list of user IDs and their matching membership and adds // them as partners. -func addPartners(m *Manager, t *testing.T) ([]*id.ID, group.Membership, gs.DhKeyList) { +func addPartners(m *Manager, t *testing.T) ([]*id.ID, group.Membership, + gs.DhKeyList) { memberIDs := make([]*id.ID, 10) members := group.Membership{m.gs.GetUser()} dkl := gs.DhKeyList{} diff --git a/groupChat/manager.go b/groupChat/manager.go index 2e2d18fadee2e5e246cf3ea42951305e14053963..4b0b3066772f7f7e72e38ef531c32f7d44b3f797 100644 --- a/groupChat/manager.go +++ b/groupChat/manager.go @@ -9,12 +9,15 @@ package groupChat import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" 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" @@ -78,7 +81,8 @@ func newManager(client *api.Client, userID *id.ID, userDhKey *cyclic.Int, receiveFunc ReceiveCallback) (*Manager, error) { // Load the group chat storage or create one if one does not exist - gStore, err := gs.NewOrLoadStore(kv, group.Member{ID: userID, DhKey: userDhKey}) + gStore, err := gs.NewOrLoadStore( + kv, group.Member{ID: userID, DhKey: userDhKey}) if err != nil { return nil, errors.Errorf(newGroupStoreErr, err) } @@ -127,6 +131,15 @@ 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 } @@ -136,17 +149,28 @@ func (m Manager) LeaveGroup(groupID *id.ID) error { return errors.Errorf(leaveGroupErr, groupID, err) } - return nil + 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 err } // GetGroups returns a list of all registered groupChat IDs. func (m Manager) GetGroups() []*id.ID { + jww.DEBUG.Print("Getting list of all groups.") return m.gs.GroupIDs() } // GetGroup returns the group with the matching ID or returns false if none // exist. func (m Manager) GetGroup(groupID *id.ID) (gs.Group, bool) { + jww.DEBUG.Printf("Getting group with ID %s.", groupID) return m.gs.Get(groupID) } diff --git a/groupChat/manager_test.go b/groupChat/manager_test.go index 0ea0f5341018fac4a24e72b999603d2a93086ed2..b3367230a80faaf3b2f752e627414914d8c8fbfe 100644 --- a/groupChat/manager_test.go +++ b/groupChat/manager_test.go @@ -31,7 +31,8 @@ func Test_newManager(t *testing.T) { requestFunc := func(g gs.Group) { requestChan <- g } receiveChan := make(chan MessageReceive) receiveFunc := func(msg MessageReceive) { receiveChan <- msg } - m, err := newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, requestFunc, receiveFunc) + m, err := newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, + requestFunc, receiveFunc) if err != nil { t.Errorf("newManager() returned an error: %+v", err) } @@ -84,7 +85,8 @@ func Test_newManager_LoadStorage(t *testing.T) { } } - m, err := newManager(nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, nil, nil) + m, err := newManager( + nil, user.ID, user.DhKey, nil, nil, nil, nil, kv, nil, nil) if err != nil { t.Errorf("newManager() returned an error: %+v", err) } @@ -125,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) @@ -271,7 +276,8 @@ func Test_newManager_LoadError(t *testing.T) { func TestManager_JoinGroup(t *testing.T) { prng := rand.New(rand.NewSource(42)) m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t) - g := newTestGroup(m.store.E2e().GetGroup(), m.store.GetUser().E2eDhPrivateKey, prng, t) + g := newTestGroup( + m.store.E2e().GetGroup(), m.store.GetUser().E2eDhPrivateKey, prng, t) err := m.JoinGroup(g) if err != nil { diff --git a/groupChat/publicFormat_test.go b/groupChat/publicFormat_test.go index 69884ff76856562e0d6f9ee3af03ad63e6eecb74..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."+ @@ -149,7 +151,8 @@ func Test_publicMsg_String(t *testing.T) { } } -// Happy path: tests that String returns the expected string for a nil publicMsg. +// Happy path: tests that String returns the expected string for a nil +// publicMsg. func Test_publicMsg_String_NilInternalMessage(t *testing.T) { pm := publicMsg{} diff --git a/groupChat/receive.go b/groupChat/receive.go index 64cf10b789b3d07bf9d1dbd82d6d76b15c91fe53..0cf5ca65f7a90a2648e1e5d30fe2a26772eb8007 100644 --- a/groupChat/receive.go +++ b/groupChat/receive.go @@ -25,7 +25,7 @@ const ( unmarshalInternalMsgErr = "failed to unmarshal group internal message: %+v" unmarshalSenderIdErr = "failed to unmarshal sender ID: %+v" unmarshalPublicMsgErr = "failed to unmarshal group cMix message contents: %+v" - findGroupKeyFpErr = "failed to find group with key fingerprint matching %s" + findGroupKeyFpErr = "no group with key fingerprint %s" genCryptKeyMacErr = "failed to generate encryption key for group " + "cMix message because MAC verification failed (epoch %d could be off)" ) @@ -45,13 +45,23 @@ func (m Manager) receive(rawMsgs chan message.Receive, stop *stoppable.Single) { jww.DEBUG.Print("Group message reception received cMix message.") // Attempt to read the message - g, msgID, timestamp, senderID, msg, err := m.readMessage(receiveMsg) + g, msgID, timestamp, senderID, msg, noFpMatch, err := + m.readMessage(receiveMsg) if err != nil { - jww.WARN.Printf("Group message reception failed to read cMix "+ - "message: %+v", err) + if noFpMatch { + jww.DEBUG.Printf("Received message not for group chat: %+v", + err) + } else { + jww.WARN.Printf("Group message reception failed to read "+ + "cMix message: %+v", err) + } continue } + jww.DEBUG.Printf("Received group message with ID %s from sender "+ + "%s in group %s with ID %s at %s.", msgID, senderID, g.Name, + g.ID, timestamp) + // If the message was read correctly, send it to the callback go m.receiveFunc(MessageReceive{ GroupID: g.ID, @@ -60,43 +70,44 @@ func (m Manager) receive(rawMsgs chan message.Receive, stop *stoppable.Single) { SenderID: senderID, RecipientID: receiveMsg.RecipientID, EphemeralID: receiveMsg.EphemeralID, - Timestamp: receiveMsg.Timestamp, + Timestamp: timestamp, RoundID: receiveMsg.RoundId, - RoundTimestamp: timestamp, + RoundTimestamp: receiveMsg.RoundTimestamp, }) } } } // readMessage returns the group, message ID, timestamp, sender ID, and message -// of a group message. The encrypted group message data is unmarshaled from a +// of a group message. The encrypted group message data is unmarshalled from a // cMix message in the message.Receive and then decrypted and the MAC is // verified. The group is found by finding the group with a matching key -// fingerprint. +// fingerprint. Returns true if the key fingerprint cannot be found; in this +// case no warning or error should be printed. func (m *Manager) readMessage(msg message.Receive) (gs.Group, group.MessageID, - time.Time, *id.ID, []byte, error) { + time.Time, *id.ID, []byte, bool, error) { // Unmarshal payload into cMix message cMixMsg := format.Unmarshal(msg.Payload) // Unmarshal cMix message contents to get public message format - publicMsg, err := unmarshalPublicMsg(cMixMsg.GetContents()) + pubMsg, err := unmarshalPublicMsg(cMixMsg.GetContents()) if err != nil { - return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, + return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, false, errors.Errorf(unmarshalPublicMsgErr, err) } // Get the group from storage via key fingerprint lookup - g, exists := m.gs.GetByKeyFp(cMixMsg.GetKeyFP(), publicMsg.GetSalt()) + g, exists := m.gs.GetByKeyFp(cMixMsg.GetKeyFP(), pubMsg.GetSalt()) if !exists { - return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, + return gs.Group{}, group.MessageID{}, time.Time{}, nil, nil, true, errors.Errorf(findGroupKeyFpErr, cMixMsg.GetKeyFP()) } // Decrypt the payload and return the messages timestamp, sender ID, and // message contents messageID, timestamp, senderID, contents, err := m.decryptMessage( - g, cMixMsg, publicMsg, msg.RoundTimestamp) - return g, messageID, timestamp, senderID, contents, err + g, cMixMsg, pubMsg, msg.RoundTimestamp) + return g, messageID, timestamp, senderID, contents, false, err } // decryptMessage decrypts the group message payload and returns its message ID, @@ -116,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 e5c7576f174f793d29ef337e092c96e4d782c371..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. @@ -26,7 +27,8 @@ const ( // receiveRequest starts the group request reception worker that waits for new // group requests to arrive. -func (m Manager) receiveRequest(rawMsgs chan message.Receive, stop *stoppable.Single) { +func (m Manager) receiveRequest(rawMsgs chan message.Receive, + stop *stoppable.Single) { jww.DEBUG.Print("Starting group message request reception worker.") for { @@ -36,7 +38,7 @@ func (m Manager) receiveRequest(rawMsgs chan message.Receive, stop *stoppable.Si stop.ToStopped() return case sendMsg := <-rawMsgs: - jww.DEBUG.Print("Group message request received send message.") + jww.DEBUG.Print("Group message request received message.") // Generate the group from the request message g, err := m.readRequest(sendMsg) @@ -49,6 +51,9 @@ func (m Manager) receiveRequest(rawMsgs chan message.Receive, stop *stoppable.Si // Call request callback with the new group if it does not already // exist if _, exists := m.GetGroup(g.ID); !exists { + jww.DEBUG.Printf("Received group request from sender %s for "+ + "group %s with ID %s.", sendMsg.Sender, g.Name, g.ID) + go m.requestFunc(g) } } @@ -71,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) } @@ -97,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/receive_test.go b/groupChat/receive_test.go index 36ea8ed2dbad4c10630630f198d07d4b6a96bf54..1592f19f2185852de918a76ba17f5dd767f62018 100644 --- a/groupChat/receive_test.go +++ b/groupChat/receive_test.go @@ -42,7 +42,8 @@ func TestManager_receive(t *testing.T) { ID: group.MessageID{0, 1, 2, 3}, Payload: contents, SenderID: sender.ID, - RoundTimestamp: timestamp.Local(), + Timestamp: timestamp.Local(), + RoundTimestamp: timestamp, } // Create cMix message and get public message @@ -51,11 +52,11 @@ func TestManager_receive(t *testing.T) { t.Errorf("Failed to create new cMix message: %+v", err) } - internalMsg, _ := newInternalMsg(cMixMsg.ContentsSize() - publicMinLen) - internalMsg.SetTimestamp(timestamp) - internalMsg.SetSenderID(m.gs.GetUser().ID) - internalMsg.SetPayload(contents) - expectedMsg.ID = group.NewMessageID(g.ID, internalMsg.Marshal()) + intlMsg, _ := newInternalMsg(cMixMsg.ContentsSize() - publicMinLen) + intlMsg.SetTimestamp(timestamp) + intlMsg.SetSenderID(m.gs.GetUser().ID) + intlMsg.SetPayload(contents) + expectedMsg.ID = group.NewMessageID(g.ID, intlMsg.Marshal()) receiveChan := make(chan message.Receive, 1) stop := stoppable.NewSingle("singleStoppable") @@ -131,7 +132,8 @@ func TestManager_receive_QuitChan(t *testing.T) { } } -// Tests that Manager.readMessage returns the message data for the correct group. +// Tests that Manager.readMessage returns the message data for the correct +// group. func TestManager_readMessage(t *testing.T) { // Create new test Manager and Group prng := rand.New(rand.NewSource(42)) @@ -163,11 +165,16 @@ func TestManager_readMessage(t *testing.T) { } m.gs.SetUser(expectedGrp.Members[4], t) - g, messageID, timestamp, senderID, contents, err := m.readMessage(receiveMsg) + g, messageID, timestamp, senderID, contents, noFpMatch, err := + m.readMessage(receiveMsg) if err != nil { t.Errorf("readMessage() returned an error: %+v", err) } + if noFpMatch { + t.Error("Fingerprint did not match when it should have.") + } + if !reflect.DeepEqual(expectedGrp, g) { t.Errorf("readMessage() returned incorrect group."+ "\nexpected: %#v\nreceived: %#v", expectedGrp, g) @@ -206,7 +213,8 @@ func TestManager_readMessage_FindGroupKpError(t *testing.T) { expectedTimestamp := netTime.Now() // Create cMix message and get public message - cMixMsg, err := m.newCmixMsg(g, expectedContents, expectedTimestamp, g.Members[4], prng) + cMixMsg, err := m.newCmixMsg( + g, expectedContents, expectedTimestamp, g.Members[4], prng) if err != nil { t.Errorf("Failed to create new cMix message: %+v", err) } @@ -223,7 +231,7 @@ func TestManager_readMessage_FindGroupKpError(t *testing.T) { expectedErr := strings.SplitN(findGroupKeyFpErr, "%", 2)[0] m.gs.SetUser(g.Members[4], t) - _, _, _, _, _, err = m.readMessage(receiveMsg) + _, _, _, _, _, _, err = m.readMessage(receiveMsg) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("readMessage() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -242,7 +250,8 @@ func TestManager_decryptMessage(t *testing.T) { expectedTimestamp := netTime.Now() // Create cMix message and get public message - msg, err := m.newCmixMsg(g, expectedContents, expectedTimestamp, g.Members[4], prng) + msg, err := m.newCmixMsg( + g, expectedContents, expectedTimestamp, g.Members[4], prng) if err != nil { t.Errorf("Failed to create new cMix message: %+v", err) } @@ -316,7 +325,7 @@ func TestManager_decryptMessage_GetCryptKeyError(t *testing.T) { } // Error path: an error is returned when the decrypted payload cannot be -// unmarshaled. +// unmarshalled. func TestManager_decryptMessage_UnmarshalInternalMsgError(t *testing.T) { // Create new test Manager and Group prng := rand.New(rand.NewSource(42)) @@ -338,11 +347,13 @@ func TestManager_decryptMessage_UnmarshalInternalMsgError(t *testing.T) { // Modify publicMsg to have invalid payload publicMsg = mapPublicMsg(publicMsg.Marshal()[:33]) - key, err := group.NewKdfKey(g.Key, group.ComputeEpoch(timestamp), publicMsg.GetSalt()) + key, err := group.NewKdfKey( + g.Key, group.ComputeEpoch(timestamp), publicMsg.GetSalt()) if err != nil { t.Errorf("failed to create new key: %+v", err) } - msg.SetMac(group.NewMAC(key, publicMsg.GetPayload(), g.DhKeys[*g.Members[4].ID])) + msg.SetMac( + group.NewMAC(key, publicMsg.GetPayload(), g.DhKeys[*g.Members[4].ID])) // Check if error is correct expectedErr := strings.SplitN(unmarshalInternalMsgErr, "%", 2)[0] @@ -364,7 +375,8 @@ func Test_getCryptKey(t *testing.T) { payload := []byte("payload") ts := netTime.Now() - expectedKey, err := group.NewKdfKey(g.Key, group.ComputeEpoch(ts.Add(5*time.Minute)), salt) + expectedKey, err := group.NewKdfKey( + g.Key, group.ComputeEpoch(ts.Add(5*time.Minute)), salt) if err != nil { t.Errorf("failed to create new key: %+v", err) } diff --git a/groupChat/send.go b/groupChat/send.go index f2baa0953555b13f335111af4d1dd7ae0e01731e..916410d1759add008948cd8d748c0bcf47cd9add 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -9,6 +9,7 @@ package groupChat import ( "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/crypto/group" @@ -35,39 +36,49 @@ const ( // Send sends a message to all group members using Client.SendManyCMIX. The // send fails if the message is too long. -func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, error) { +func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, time.Time, + error) { + // 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) + messages, err := m.createMessages(groupID, message, timeNow) if err != nil { - return 0, errors.Errorf(newCmixMsgErr, err) + 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, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) + return 0, time.Time{}, + errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err) } - return rid, nil + jww.DEBUG.Printf("Sent message to %d members in group %s at %s.", + len(messages), groupID, timeNow) + + return rid, timeNow, nil } // createMessages generates a list of cMix messages and a list of corresponding // recipient IDs. -func (m *Manager) createMessages(groupID *id.ID, msg []byte) (map[id.ID]format.Message, error) { - timeNow := netTime.Now() +func (m *Manager) createMessages(groupID *id.ID, msg []byte, + timestamp time.Time) (map[id.ID]format.Message, error) { g, exists := m.gs.Get(groupID) if !exists { return map[id.ID]format.Message{}, errors.Errorf(newNoGroupErr, groupID) } - return m.newMessages(g, msg, timeNow) + return m.newMessages(g, msg, timestamp) } // 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) @@ -122,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 @@ -149,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]) @@ -171,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 70c5501ec7c6492a1b33f5a5309b2602c38ad06e..edcecc77f1b5516a9ed75d9481738210374b88e4 100644 --- a/groupChat/sendRequests.go +++ b/groupChat/sendRequests.go @@ -10,6 +10,7 @@ package groupChat import ( "github.com/golang/protobuf/proto" "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" @@ -34,6 +35,8 @@ func (m Manager) ResendRequest(groupID *id.ID) ([]id.Round, RequestStatus, error return nil, NotSent, errors.Errorf(resendGroupIdErr, groupID) } + jww.DEBUG.Printf("Resending group requests for group %s.", groupID) + return m.sendRequests(g) } @@ -47,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) @@ -98,6 +102,9 @@ func (m Manager) sendRequests(g gs.Group) ([]id.Round, RequestStatus, error) { strings.Join(errs, "\n")) } + jww.DEBUG.Printf("Sent group request to %d members in group %q with ID %s.", + len(g.Members), g.Name, g.ID) + // If all sends succeeded, return a list of roundIDs return roundIdMap2List(roundIDs), AllSent, nil } diff --git a/groupChat/sendRequests_test.go b/groupChat/sendRequests_test.go index 56ca284fbc66cb78622388bd11d0454db3280bce..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) @@ -185,8 +187,8 @@ func TestManager_sendRequests_SendAllFail(t *testing.T) { } } -// Tests that Manager.sendRequests returns the correct status when some of the -// sends fail. +// Tests that Manager.sendRequests returns the correct status when some sends +// fail. func TestManager_sendRequests_SendPartialSent(t *testing.T) { prng := rand.New(rand.NewSource(42)) m, g := newTestManagerWithStore(prng, 10, 2, nil, nil, t) diff --git a/groupChat/send_test.go b/groupChat/send_test.go index 1db5cec791e7790bf519ab98030a088f423b076e..395ffc3ee9e3b640b38e88e0b2e11331d9e8036d 100644 --- a/groupChat/send_test.go +++ b/groupChat/send_test.go @@ -29,7 +29,7 @@ func TestManager_Send(t *testing.T) { message := []byte("Group chat message.") sender := m.gs.GetUser().DeepCopy() - _, err := m.Send(g.ID, message) + _, _, err := m.Send(g.ID, message) if err != nil { t.Errorf("Send() returned an error: %+v", err) } @@ -109,7 +109,7 @@ func TestManager_Send_CmixMessageError(t *testing.T) { expectedErr := strings.SplitN(newCmixMsgErr, "%", 2)[0] // Send message - _, err := m.Send(g.ID, make([]byte, 400)) + _, _, err := m.Send(g.ID, make([]byte, 400)) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("Send() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -124,7 +124,7 @@ func TestManager_Send_SendManyCMIXError(t *testing.T) { expectedErr := strings.SplitN(sendManyCmixErr, "%", 2)[0] // Send message - _, err := m.Send(g.ID, []byte("message")) + _, _, err := m.Send(g.ID, []byte("message")) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("Send() failed to return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -136,14 +136,15 @@ func TestManager_Send_SendManyCMIXError(t *testing.T) { } } -// Tests that Manager.createMessages generates the messages for the correct group. +// Tests that Manager.createMessages generates the messages for the correct +// group. func TestManager_createMessages(t *testing.T) { prng := rand.New(rand.NewSource(42)) m, g := newTestManagerWithStore(prng, 10, 0, nil, nil, t) message := []byte("Test group message.") sender := m.gs.GetUser() - messages, err := m.createMessages(g.ID, message) + messages, err := m.createMessages(g.ID, message, netTime.Now()) if err != nil { t.Errorf("createMessages() returned an error: %+v", err) } @@ -203,7 +204,8 @@ func TestManager_createMessages_InvalidGroupIdError(t *testing.T) { m, _ := newTestManagerWithStore(prng, 10, 0, nil, nil, t) // Read message and make sure the error is expected - _, err := m.createMessages(id.NewIdFromString("invalidID", id.Group, t), nil) + _, err := m.createMessages( + id.NewIdFromString("invalidID", id.Group, t), nil, time.Time{}) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("createMessages() did not return the expected error."+ "\nexpected: %s\nreceived: %+v", expectedErr, err) @@ -373,7 +375,8 @@ func TestGroup_newCmixMsg_SaltReaderError(t *testing.T) { expectedErr := strings.SplitN(saltReadErr, "%", 2)[0] m := &Manager{store: storage.InitTestingSession(t)} - _, err := m.newCmixMsg(gs.Group{}, []byte{}, time.Time{}, group.Member{}, strings.NewReader("")) + _, err := m.newCmixMsg(gs.Group{}, + []byte{}, time.Time{}, group.Member{}, strings.NewReader("")) if err == nil || !strings.Contains(err.Error(), expectedErr) { t.Errorf("newCmixMsg() failed to return the expected error"+ "\nexpected: %s\nreceived: %+v", expectedErr, err) diff --git a/groupChat/utils_test.go b/groupChat/utils_test.go index d43ada0dc827fcff5fe253d9d89189abf588f778..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" @@ -101,7 +102,8 @@ func newTestManagerWithStore(rng *rand.Rand, numGroups int, sendErr int, } // getMembership returns a Membership with random members for testing. -func getMembership(size int, uid *id.ID, pubKey *cyclic.Int, grp *cyclic.Group, prng *rand.Rand, t *testing.T) group.Membership { +func getMembership(size int, uid *id.ID, pubKey *cyclic.Int, grp *cyclic.Group, + prng *rand.Rand, t *testing.T) group.Membership { contacts := make([]contact.Contact, size) for i := range contacts { randId, _ := id.NewRandomID(prng, id.User) @@ -123,7 +125,8 @@ func getMembership(size int, uid *id.ID, pubKey *cyclic.Int, grp *cyclic.Group, } // newTestGroup generates a new group with random values for testing. -func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, t *testing.T) gs.Group { +func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, + t *testing.T) gs.Group { // Generate name from base 64 encoded random data nameBytes := make([]byte, 16) rng.Read(nameBytes) @@ -137,7 +140,8 @@ func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, t *tes membership := getMembership(10, id.NewIdFromString("userID", id.User, t), randCycInt(rng), grp, rng, t) - dkl := gs.GenerateDhKeyList(id.NewIdFromString("userID", id.User, t), privKey, membership, grp) + dkl := gs.GenerateDhKeyList( + id.NewIdFromString("userID", id.User, t), privKey, membership, grp) idPreimage, err := group.NewIdPreimage(rng) if err != nil { @@ -153,7 +157,7 @@ func newTestGroup(grp *cyclic.Group, privKey *cyclic.Int, rng *rand.Rand, t *tes 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. @@ -187,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. @@ -241,7 +245,8 @@ func (tnm *testNetworkManager) GetE2eMsg(i int) message.Send { return tnm.e2eMessages[i] } -func (tnm *testNetworkManager) SendE2E(msg message.Send, _ params.E2E, _ *stoppable.Single) ([]id.Round, e2e.MessageID, time.Time, error) { +func (tnm *testNetworkManager) SendE2E(msg message.Send, _ params.E2E, + _ *stoppable.Single) ([]id.Round, e2e.MessageID, time.Time, error) { tnm.Lock() defer tnm.Unlock() @@ -269,7 +274,8 @@ func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id return 0, ephemeral.Id{}, nil } -func (tnm *testNetworkManager) SendManyCMIX(messages map[id.ID]format.Message, _ params.CMIX) (id.Round, []ephemeral.Id, error) { +func (tnm *testNetworkManager) SendManyCMIX(messages map[id.ID]format.Message, + _ params.CMIX) (id.Round, []ephemeral.Id, error) { if tnm.sendErr == 1 { return 0, nil, errors.New("SendManyCMIX error") } @@ -284,8 +290,8 @@ func (tnm *testNetworkManager) SendManyCMIX(messages map[id.ID]format.Message, _ type dummyEventMgr struct{} -func (d *dummyEventMgr) Report(p int, a, b, c string) {} -func (t *testNetworkManager) GetEventManager() interfaces.EventManager { +func (d *dummyEventMgr) Report(int, string, string, string) {} +func (tnm *testNetworkManager) GetEventManager() interfaces.EventManager { return &dummyEventMgr{} } 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/gateway/hostPool.go b/network/gateway/hostPool.go index 4739d5052494f5c4bea99738970885634941d0ad..5bb0b82044058f8c51f055482cdd00a1b6bf37d9 100644 --- a/network/gateway/hostPool.go +++ b/network/gateway/hostPool.go @@ -581,24 +581,27 @@ func (h *HostPool) updateConns() error { // Filter out gateway IDs newMap = h.getFilter()(newMap, h.ndf) + // Keep track of the old NDF set + oldMap := h.ndfMap + // Update the internal NDF set + h.ndfMap = newMap + // Handle adding Gateways for gwId, ndfIdx := range newMap { - if _, ok := h.ndfMap[gwId]; !ok { + if _, ok := oldMap[gwId]; !ok { // If GwId in newMap is not in ndfMap, add the Gateway h.addGateway(gwId.DeepCopy(), ndfIdx) } } // Handle removing Gateways - for gwId := range h.ndfMap { + for gwId := range oldMap { if _, ok := newMap[gwId]; !ok { // If GwId in ndfMap is not in newMap, remove the Gateway h.removeGateway(gwId.DeepCopy()) } } - // Update the internal NDF set - h.ndfMap = newMap return nil } 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/single/manager_test.go b/single/manager_test.go index d39febc07a41154f3cb164af1b4755325bcef332..a1658a0b733bb21df35e17deef53072a76f7cf21 100644 --- a/single/manager_test.go +++ b/single/manager_test.go @@ -186,6 +186,11 @@ func TestManager_StartProcesses_Stop(t *testing.T) { t.Errorf("Failed to close: %+v", err) } + // Wait for the stoppable to close + for !stop.IsStopped() { + time.Sleep(10 * time.Millisecond) + } + m.swb.(*switchboard.Switchboard).Speak(receiveMsg) timer := time.NewTimer(50 * time.Millisecond) 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 9b949f240b11f1442c7228ecd695572212574886..0b57d6d9f41f1e19b6c1ce6cffba1965c420c3e3 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 @@ -155,6 +157,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 } @@ -233,6 +239,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 } @@ -315,6 +326,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) @@ -418,5 +436,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 2adc57a4f69f9d445298d4c76cc7f98d7ca96a65..2dc2dd2a8724227423df2ff028809746aa49cc8f 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" "time" ) @@ -22,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()} @@ -77,5 +75,12 @@ func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback, DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey), } + if lookupResponse.Username != "" { + c.Facts = fact.FactList{{ + Fact: lookupResponse.Username, + T: fact.Username, + }} + } + go callback(c, nil) } diff --git a/ud/search.go b/ud/search.go index 86c51c5b596a8810954723f9bb26fb27b969b14a..e8db5749ace91b445f56b7f71ace9c8b5165b886 100644 --- a/ud/search.go +++ b/ud/search.go @@ -14,7 +14,7 @@ import ( // SearchTag specifies which callback to trigger when UD receives a search // request. -const SearchTag = "xxNetwork_UdLookup" +const SearchTag = "xxNetwork_UdSearch" // TODO: reconsider where this comes from const maxSearchMessages = 20 @@ -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) diff --git a/ud/udMessages.pb.go b/ud/udMessages.pb.go index 82477b3fdd3d3a968997295895f227e1096c7501..0aae2063325f57b73c42ff53e30020225aefcd7e 100644 --- a/ud/udMessages.pb.go +++ b/ud/udMessages.pb.go @@ -266,6 +266,7 @@ func (m *LookupSend) GetUserID() []byte { // Message sent from UDB for looking up a user type LookupResponse struct { PubKey []byte `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -304,6 +305,13 @@ func (m *LookupResponse) GetPubKey() []byte { return nil } +func (m *LookupResponse) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + func (m *LookupResponse) GetError() string { if m != nil { return m.Error @@ -325,23 +333,23 @@ func init() { } var fileDescriptor_9e0cfdc16fb09bb6 = []byte{ - // 281 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4b, 0xf4, 0x30, - 0x10, 0xc5, 0xc9, 0x6e, 0xbb, 0x5f, 0x77, 0xbe, 0xa5, 0x4a, 0x10, 0x29, 0x9e, 0x4a, 0xf4, 0x50, - 0x04, 0x0b, 0xae, 0x77, 0x0f, 0x2a, 0xa2, 0xa8, 0x97, 0xec, 0xcd, 0x5b, 0xb6, 0x1d, 0xb7, 0x22, - 0x36, 0x21, 0x93, 0x1e, 0xf6, 0xee, 0x1f, 0x2e, 0x4d, 0x63, 0x17, 0x61, 0xbd, 0xe5, 0xcd, 0xcc, - 0x8f, 0x79, 0xf3, 0x02, 0x87, 0x5d, 0xfd, 0x82, 0x44, 0x6a, 0x83, 0x54, 0x1a, 0xab, 0x9d, 0xe6, - 0xb1, 0x51, 0x96, 0x50, 0x2c, 0x21, 0x79, 0x50, 0xd4, 0xdc, 0xab, 0xca, 0x71, 0x0e, 0x51, 0xa3, - 0xa8, 0xc9, 0x58, 0xce, 0x8a, 0x85, 0xf4, 0xef, 0xbe, 0xe6, 0xb6, 0x06, 0xb3, 0x49, 0xce, 0x8a, - 0x58, 0xfa, 0xb7, 0xf8, 0x62, 0xf0, 0xef, 0x56, 0xb7, 0xae, 0x67, 0x8e, 0x61, 0xd6, 0x11, 0xda, - 0xc7, 0xbb, 0x40, 0x05, 0xd5, 0xd7, 0x4d, 0xb7, 0x7e, 0xc2, 0xad, 0x27, 0x17, 0x32, 0x28, 0x7e, - 0x02, 0x49, 0x3f, 0xd1, 0xaa, 0x4f, 0xcc, 0xa6, 0x39, 0x2b, 0xe6, 0x72, 0xd4, 0xfc, 0x02, 0xe6, - 0xce, 0xbe, 0x6f, 0x7a, 0x2f, 0x94, 0x45, 0xf9, 0xb4, 0xf8, 0xbf, 0x3c, 0x28, 0xbd, 0xcd, 0xf2, - 0xc7, 0xa3, 0xdc, 0x4d, 0x88, 0x4b, 0x80, 0x15, 0x2a, 0x5b, 0x35, 0x2b, 0x6c, 0x6b, 0x7e, 0x0a, - 0xd1, 0x9b, 0xaa, 0x5c, 0xc6, 0xf6, 0x73, 0xbe, 0x29, 0x24, 0xa4, 0x03, 0x22, 0x91, 0x8c, 0x6e, - 0x09, 0xf9, 0x39, 0x24, 0xd5, 0x70, 0x0a, 0x05, 0x34, 0x0d, 0x68, 0xb8, 0x50, 0x8e, 0x7d, 0x7e, - 0x04, 0x31, 0x5a, 0xab, 0x6d, 0x30, 0x3e, 0x08, 0x71, 0x06, 0xf0, 0xac, 0xf5, 0x47, 0x67, 0xbc, - 0x8d, 0x3f, 0xf2, 0x10, 0xd7, 0x90, 0x0e, 0x53, 0xe3, 0xe6, 0x5d, 0x42, 0xec, 0x57, 0x42, 0x7b, - 0xb7, 0xdc, 0x44, 0xaf, 0x93, 0xae, 0x5e, 0xcf, 0xfc, 0xdf, 0x5d, 0x7d, 0x07, 0x00, 0x00, 0xff, - 0xff, 0x1f, 0xee, 0x62, 0x3e, 0xcf, 0x01, 0x00, 0x00, + // 283 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x41, 0x4b, 0xc3, 0x40, + 0x10, 0x85, 0xd9, 0x36, 0xad, 0xe9, 0x58, 0xa2, 0x2c, 0x22, 0xc1, 0x53, 0x58, 0x3d, 0x04, 0xc1, + 0x80, 0xf5, 0x1f, 0xa8, 0x88, 0xa2, 0x5e, 0xb6, 0xb7, 0xde, 0xb6, 0xc9, 0xd8, 0x88, 0x98, 0x5d, + 0x76, 0x36, 0x87, 0xde, 0xfd, 0xe1, 0x92, 0xcd, 0x9a, 0x82, 0xb4, 0xb7, 0x79, 0x33, 0xf3, 0xb1, + 0x6f, 0xde, 0xc2, 0x69, 0x5b, 0xbd, 0x23, 0x91, 0xda, 0x20, 0x15, 0xc6, 0x6a, 0xa7, 0xf9, 0xc4, + 0x28, 0x4b, 0x28, 0x16, 0x10, 0x3f, 0x2b, 0xaa, 0x9f, 0x54, 0xe9, 0x38, 0x87, 0xa8, 0x56, 0x54, + 0xa7, 0x2c, 0x63, 0xf9, 0x5c, 0xfa, 0xba, 0xeb, 0xb9, 0xad, 0xc1, 0x74, 0x94, 0xb1, 0x7c, 0x22, + 0x7d, 0x2d, 0x7e, 0x18, 0x1c, 0x3d, 0xe8, 0xc6, 0x75, 0xcc, 0x39, 0x4c, 0x5b, 0x42, 0xfb, 0xf2, + 0x18, 0xa8, 0xa0, 0xba, 0xbe, 0x69, 0xd7, 0xaf, 0xb8, 0xf5, 0xe4, 0x5c, 0x06, 0xc5, 0x2f, 0x20, + 0xee, 0x36, 0x1a, 0xf5, 0x8d, 0xe9, 0x38, 0x63, 0xf9, 0x4c, 0x0e, 0x9a, 0xdf, 0xc0, 0xcc, 0xd9, + 0xcf, 0x4d, 0xe7, 0x85, 0xd2, 0x28, 0x1b, 0xe7, 0xc7, 0x8b, 0x93, 0xc2, 0xdb, 0x2c, 0xfe, 0x3c, + 0xca, 0xdd, 0x86, 0xb8, 0x05, 0x58, 0xa2, 0xb2, 0x65, 0xbd, 0xc4, 0xa6, 0xe2, 0x97, 0x10, 0x7d, + 0xa8, 0xd2, 0xa5, 0x6c, 0x3f, 0xe7, 0x87, 0x42, 0x42, 0xd2, 0x23, 0x12, 0xc9, 0xe8, 0x86, 0x90, + 0x5f, 0x43, 0x5c, 0xf6, 0xa7, 0x50, 0x40, 0x93, 0x80, 0x86, 0x0b, 0xe5, 0x30, 0xe7, 0x67, 0x30, + 0x41, 0x6b, 0xb5, 0x0d, 0xc6, 0x7b, 0x21, 0xae, 0x00, 0xde, 0xb4, 0xfe, 0x6a, 0x8d, 0xb7, 0x71, + 0x20, 0x0f, 0xb1, 0x82, 0xa4, 0xdf, 0x1a, 0x5e, 0xde, 0x25, 0xc4, 0x0e, 0x26, 0x34, 0xfa, 0x97, + 0xd0, 0x5e, 0x07, 0xf7, 0xd1, 0x6a, 0xd4, 0x56, 0xeb, 0xa9, 0xff, 0xd7, 0xbb, 0xdf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x5a, 0xee, 0x38, 0xba, 0xeb, 0x01, 0x00, 0x00, } diff --git a/ud/udMessages.proto b/ud/udMessages.proto index c8f993c8cb08d5fbe40b9c9a782e5ddbc8e4da65..e6905f8359be96a7f331561495d44b20c7e1ab17 100644 --- a/ud/udMessages.proto +++ b/ud/udMessages.proto @@ -48,5 +48,6 @@ message LookupSend { // Message sent from UDB for looking up a user message LookupResponse { bytes pubKey = 1; + string username = 2; string error = 3; }