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/README.md b/README.md index a5041e876b23c787616d2f260830f2aca19de0d3..cb6d69e09ad7aab6c7676b2aae8c310539d286b4 100644 --- a/README.md +++ b/README.md @@ -145,59 +145,49 @@ Usage: client [command] Available Commands: - generate Generates version and dependency information for the - Elixxir binary + generate Generates version and dependency information for the Elixxir binary + getndf Download the network definition file from the network and print it. + group Group commands for cMix client help Help about any command - version Print the version and dependency information for the - Elixxir binary + init Initialize a user ID but do not connect to the network + proto Load client with a proto client JSON file. + single Send and respond to single-use messages. + ud Register for and search users using the xx network user discovery service. + version Print the version and dependency information for the Elixxir binary Flags: - --accept-channel Accept the channel request for the - corresponding recipient ID - --delete-channel Delete the channel information for the - corresponding recipient ID + --accept-channel Accept the channel request for the corresponding recipient ID + --auth-timeout uint The number of seconds to wait for an authentication channelto confirm (default 120) + --delete-channel Delete the channel information for the corresponding recipient ID --destfile string Read this contact file for the destination id - -d, --destid string ID to send message to (if below 40, will be - precanned. Use '0x' or 'b64:' for hex and - base64 representations) (default "0") - --forceHistoricalRounds Force all rounds to be sent to historical - round retrieval - --forceMessagePickupRetry Enable a mechanism which forces a 50% chance - of no message pickup, instead triggering the - message pickup retry mechanism + -d, --destid string ID to send message to (if below 40, will be precanned. Use '0x' or 'b64:' for hex and base64 representations) (default "0") + --e2eMaxKeys uint Max keys used before blocking until a rekey completes (default 800) + --e2eMinKeys uint Minimum number of keys used before requesting rekey (default 500) + --e2eNumReKeys uint Number of rekeys reserved for rekey operations (default 16) + --forceHistoricalRounds Force all rounds to be sent to historical round retrieval + --forceMessagePickupRetry Enable a mechanism which forces a 50% chance of no message pickup, instead triggering the message pickup retry mechanism -h, --help help for client - -l, --log string Path to the log output path (- is stdout) - (default "-") + -l, --log string Path to the log output path (- is stdout) (default "-") + -v, --logLevel uint Verbose mode for debugging -m, --message string Message to send - -n, --ndf string Path to the network definition JSON file - (default "ndf.json") + -n, --ndf string Path to the network definition JSON file (default "ndf.json") -p, --password string Password to the session file - --receiveCount uint How many messages we should wait for before - quitting (default 1) - --regcode string Registration code (optional) - --sendCount uint The number of times to send the message - (default 1) - --sendDelay uint The delay between sending the messages in ms - (default 500) - --sendid uint Use precanned user id (must be between 1 and - 40, inclusive) - --slowPolling bool Enables polling for all network updates and RSA signed rounds. - Defaults to true (filtered updates with ECC signed rounds) if not set - - -s, --session string Sets the initial directory for client storage - --unsafe Send raw, unsafe messages without e2e - encryption. - --unsafe-channel-creation Turns off the user identity authenticated - channel check, automatically approving - authenticated channels - --verboseRoundTracking Verbose round tracking, keeps track and prints - all rounds the client was aware of while running. - Defaults to false if not set. - -v, --logLevel uint Level of debugging to print (0 = info, - 1 = debug, >1 = trace). (Default info) - --waitTimeout uint The number of seconds to wait for messages to - arrive (default 15) - -w, --writeContact string Write the contact file for this user to this + --profile-cpu string Enable cpu profiling to this file + --protoUserOut string Path to which a normally constructed client will write proto user JSON file (default "protoUser.json") + --protoUserPath string Path to proto user JSON file containing cryptographic primitives the client will load (default "protoUser.json") + --receiveCount uint How many messages we should wait for before quitting (default 1) + --regcode string Identity code (optional) + --send-auth-request Send an auth request to the specified destination and waitfor confirmation + --sendCount uint The number of times to send the message (default 1) + --sendDelay uint The delay between sending the messages in ms (default 500) + --sendid uint Use precanned user id (must be between 1 and 40, inclusive) + -s, --session string Sets the initial storage directory for client session data + --slowPolling Enables polling for unfiltered network updates with RSA signatures + --unsafe Send raw, unsafe messages without e2e encryption. + --unsafe-channel-creation Turns off the user identity authenticated channel check, automatically approving authenticated channels + --verboseRoundTracking Verbose round tracking, keeps track and prints all rounds the client was aware of while running. Defaults to false if not set. + --waitTimeout uint The number of seconds to wait for messages to arrive (default 15) + -w, --writeContact string Write contact information, if any, to this file, defaults to stdout (default "-") file Use "client [command] --help" for more information about a command. diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 746572e94e73eb85c16dc60851adb04d901297a1..b302f78d46b81b9ebdb20d9f3e7e08f9ce2abb47 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -12,6 +12,8 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/auth" "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/primitives/id" @@ -103,6 +105,27 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co // the channel c.network.CheckGarbledMessages() + //add the e2e and rekey firngeprints + //e2e + sessionPartner, err := c.storage.E2e().GetPartner(precan.ID) + if err != nil { + jww.FATAL.Panicf("Cannot find %s right after creating: %+v", precan.ID, err) + } + me := c.storage.GetUser().ReceptionID + + c.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetE2EPreimage(), + Type: preimage.E2e, + Source: precan.ID[:], + }, me) + + //rekey + c.storage.GetEdge().Add(edge.Preimage{ + Data: sessionPartner.GetRekeyPreimage(), + Type: preimage.Rekey, + Source: precan.ID[:], + }, me) + return precan, err } diff --git a/api/client.go b/api/client.go index 012e980a5d946c5dd632b896a9d26037d9b308bd..ef162c13555cdba73c64ce38cc970c343c263eca 100644 --- a/api/client.go +++ b/api/client.go @@ -8,17 +8,20 @@ package api import ( + "encoding/json" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "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" @@ -72,7 +75,8 @@ type Client struct { // with the network. Note that this does not register a username/identity, but // merely creates a new cryptographic identity for adding such information // at a later date. -func NewClient(ndfJSON, storageDir string, password []byte, registrationCode string) error { +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, 1024, csprng.NewSystemRNG) @@ -88,7 +92,7 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str protoUser := createNewUser(rngStreamGen, cmixGrp, e2eGrp) jww.DEBUG.Printf("User generation took: %s", time.Now().Sub(start)) - err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { return err @@ -103,7 +107,8 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str // with the network. Note that this does not register a username/identity, but // merely creates a new cryptographic identity for adding such information // at a later date. -func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password []byte) error { +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, 1024, csprng.NewSystemRNG) @@ -118,7 +123,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp) - err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, true, "") if err != nil { return err @@ -132,7 +137,8 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ // with the network. Note that this does not register a username/identity, but // merely creates a new cryptographic identity for adding such information // at a later date. -func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error { +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, 1024, csprng.NewSystemRNG) @@ -147,7 +153,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCo protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix) - err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, + _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, false, registrationCode) if err != nil { return err @@ -193,6 +199,55 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) ( return c, nil } +// NewProtoClient_Unsafe initializes a client object from a JSON containing +// predefined cryptographic which defines a user. This is designed for some +// specific deployment procedures and is generally unsafe. +func NewProtoClient_Unsafe(ndfJSON, storageDir string, password, + protoClientJSON []byte) error { + jww.INFO.Printf("NewProtoClient_Unsafe") + + // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) + rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG) + + // Parse the NDF + def, err := parseNDF(ndfJSON) + if err != nil { + return err + } + + cmixGrp, e2eGrp := decodeGroups(def) + + // Pull the proto user from the JSON + protoUser := &user.Proto{} + err = json.Unmarshal(protoClientJSON, protoUser) + if err != nil { + return err + } + + // Initialize a user object for storage set up + usr := user.NewUserFromProto(protoUser) + + // Set up storage + storageSess, err := checkVersionAndSetupStorage(def, storageDir, password, usr, + cmixGrp, e2eGrp, rngStreamGen, false, protoUser.RegCode) + if err != nil { + return err + } + + // Set registration values in storage + storageSess.User().SetReceptionRegistrationValidationSignature(protoUser.ReceptionRegValidationSig) + storageSess.User().SetTransmissionRegistrationValidationSignature(protoUser.TransmissionRegValidationSig) + storageSess.User().SetRegistrationTimestamp(protoUser.RegistrationTimestamp) + + //move the registration state to indicate registered with registration on proto client + err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete) + if err != nil { + return err + } + + return nil +} + // Login initializes a client object from existing storage. func Login(storageDir string, password []byte, parameters params.Network) (*Client, error) { jww.INFO.Printf("Login()") @@ -234,7 +289,8 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie hp.KaClientOpts.Time = time.Duration(math.MaxInt64) hp.AuthEnabled = false hp.MaxRetries = 5 - _, err = c.comms.AddHost(&id.NotificationBot, def.Notification.Address, []byte(def.Notification.TlsCertificate), hp) + _, err = c.comms.AddHost(&id.NotificationBot, def.Notification.Address, + []byte(def.Notification.TlsCertificate), hp) if err != nil { jww.WARN.Printf("Failed adding host for notifications: %+v", err) } @@ -380,7 +436,9 @@ func (c *Client) registerFollower() error { } //register the core follower service - err = c.followerServices.add(func() (stoppable.Stoppable, error) { return c.network.Follow(cer) }) + err = c.followerServices.add(func() (stoppable.Stoppable, error) { + return c.network.Follow(cer) + }) if err != nil { return errors.WithMessage(err, "Failed to start following "+ "the network") @@ -537,31 +595,68 @@ func (c *Client) GetNodeRegistrationStatus() (int, int, error) { cmixStore := c.storage.Cmix() var numRegistered int + var numStale = 0 for i, n := range nodes { nid, err := id.Unmarshal(n.ID) if err != nil { return 0, 0, errors.Errorf("Failed to unmarshal node ID %v "+ "(#%d): %s", n.ID, i, err.Error()) } + if n.Status == ndf.Stale { + numStale += 1 + continue + } if cmixStore.Has(nid) { numRegistered++ } } // Get the number of in progress node registrations - return numRegistered, len(nodes), nil + return numRegistered, len(nodes) - numStale, nil } // 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 } @@ -664,13 +759,15 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) { // checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient // it checks client version and creates a new storage for user data -func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, password []byte, - protoUser user.User, cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator, - isPrecanned bool, registrationCode string) error { +func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, + storageDir string, password []byte, + protoUser user.User, + cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator, + isPrecanned bool, registrationCode string) (*storage.Session, error) { // Get current client version currentVersion, err := version.ParseVersion(SEMVER) if err != nil { - return errors.WithMessage(err, "Could not parse version string.") + return nil, errors.WithMessage(err, "Could not parse version string.") } // Create Storage @@ -678,7 +775,7 @@ func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, storageSess, err := storage.New(storageDir, passwordStr, protoUser, currentVersion, cmixGrp, e2eGrp, rngStreamGen) if err != nil { - return err + return nil, err } // Save NDF to be used in the future @@ -694,10 +791,17 @@ 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 "+ + return nil, errors.WithMessage(err, "Failed to denote state "+ "change in session") } - return nil + return storageSess, nil } diff --git a/api/notifications.go b/api/notifications.go index 2a7d1b7c107128360f89930c8bf0bcb16dfc54de..58e97190e469a7d21d9175b9b84ddaff89b75e2c 100644 --- a/api/notifications.go +++ b/api/notifications.go @@ -43,7 +43,7 @@ func (c *Client) RegisterForNotifications(token string) error { TransmissionSalt: c.GetUser().TransmissionSalt, TransmissionRsaSig: c.GetStorage().User().GetTransmissionRegistrationValidationSignature(), IIDTransmissionRsaSig: sig, - RegistrationTimestamp: c.GetUser().RegistrationTimestamp.UnixNano(), + RegistrationTimestamp: c.GetUser().RegistrationTimestamp, }) if err != nil { err := errors.Errorf( diff --git a/api/permissioning.go b/api/permissioning.go index ef94e61daf315472ecde87eddc360335c08b445d..85aac34b93f333f4994c49b1dac1ec868965807a 100644 --- a/api/permissioning.go +++ b/api/permissioning.go @@ -8,7 +8,9 @@ package api import ( + "encoding/json" "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces/user" "gitlab.com/elixxir/client/storage" ) @@ -47,3 +49,41 @@ func (c *Client) registerWithPermissioning() error { } return nil } + +// ConstructProtoUerFile is a helper function which is used for proto client testing. +// This is used for development testing. +func (c *Client) ConstructProtoUerFile() ([]byte, error) { + + //load the registration code + regCode, err := c.storage.GetRegCode() + if err != nil { + return nil, errors.WithMessage(err, "failed to register with "+ + "permissioning") + } + + Usr := user.Proto{ + TransmissionID: c.GetUser().TransmissionID, + TransmissionSalt: c.GetUser().TransmissionSalt, + TransmissionRSA: c.GetUser().TransmissionRSA, + ReceptionID: c.GetUser().ReceptionID, + ReceptionSalt: c.GetUser().ReceptionSalt, + ReceptionRSA: c.GetUser().ReceptionRSA, + Precanned: c.GetUser().Precanned, + RegistrationTimestamp: c.GetUser().RegistrationTimestamp, + RegCode: regCode, + TransmissionRegValidationSig: c.storage.User().GetTransmissionRegistrationValidationSignature(), + ReceptionRegValidationSig: c.storage.User().GetReceptionRegistrationValidationSignature(), + CmixDhPrivateKey: c.GetStorage().Cmix().GetDHPrivateKey(), + CmixDhPublicKey: c.GetStorage().Cmix().GetDHPublicKey(), + E2eDhPrivateKey: c.GetStorage().E2e().GetDHPrivateKey(), + E2eDhPublicKey: c.GetStorage().E2e().GetDHPublicKey(), + } + + jsonBytes, err := json.Marshal(Usr) + if err != nil { + return nil, errors.WithMessage(err, "failed to register with "+ + "permissioning") + } + + return jsonBytes, nil +} diff --git a/api/user.go b/api/user.go index ef8eb1092ee61e8e766c00d2979cb4c20d108d82..1b22a0877235636c55bbd2be922cddfcf4fe2b39 100644 --- a/api/user.go +++ b/api/user.go @@ -36,6 +36,62 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U var cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt []byte + cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt, + transmissionRsaKey, receptionRsaKey = createDhKeys(rng, cmix, e2e) + + // Salt, UID, etc gen + stream := rng.GetStream() + transmissionSalt = make([]byte, SaltSize) + + n, err := stream.Read(transmissionSalt) + + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("transmissionSalt size too small: %d", n) + } + + receptionSalt = make([]byte, SaltSize) + + n, err = stream.Read(receptionSalt) + + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + if n != SaltSize { + jww.FATAL.Panicf("transmissionSalt size too small: %d", n) + } + + stream.Close() + + transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + receptionID, err := xx.NewID(receptionRsaKey.GetPublic(), receptionSalt, id.User) + if err != nil { + jww.FATAL.Panicf(err.Error()) + } + + return user.User{ + TransmissionID: transmissionID.DeepCopy(), + TransmissionSalt: transmissionSalt, + TransmissionRSA: transmissionRsaKey, + ReceptionID: receptionID.DeepCopy(), + ReceptionSalt: receptionSalt, + ReceptionRSA: receptionRsaKey, + Precanned: false, + CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes), + E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), + } +} + +func createDhKeys(rng *fastRNG.StreamGenerator, + cmix, e2e *cyclic.Group) (cMixKeyBytes, e2eKeyBytes, + transmissionSalt, receptionSalt []byte, + transmissionRsaKey, receptionRsaKey *rsa.PrivateKey) { wg := sync.WaitGroup{} wg.Add(4) @@ -93,53 +149,8 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U }() wg.Wait() - // Salt, UID, etc gen - stream := rng.GetStream() - transmissionSalt = make([]byte, SaltSize) - - n, err := stream.Read(transmissionSalt) - - if err != nil { - jww.FATAL.Panicf(err.Error()) - } - if n != SaltSize { - jww.FATAL.Panicf("transmissionSalt size too small: %d", n) - } - - receptionSalt = make([]byte, SaltSize) - - n, err = stream.Read(receptionSalt) - - if err != nil { - jww.FATAL.Panicf(err.Error()) - } - if n != SaltSize { - jww.FATAL.Panicf("transmissionSalt size too small: %d", n) - } - - stream.Close() - - transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User) - if err != nil { - jww.FATAL.Panicf(err.Error()) - } - - receptionID, err := xx.NewID(receptionRsaKey.GetPublic(), receptionSalt, id.User) - if err != nil { - jww.FATAL.Panicf(err.Error()) - } + return - return user.User{ - TransmissionID: transmissionID.DeepCopy(), - TransmissionSalt: transmissionSalt, - TransmissionRSA: transmissionRsaKey, - ReceptionID: receptionID.DeepCopy(), - ReceptionSalt: receptionSalt, - ReceptionRSA: receptionRsaKey, - Precanned: false, - CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes), - E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), - } } // TODO: Add precanned user code structures here. diff --git a/api/version_vars.go b/api/version_vars.go index fbf2295851c49dc762e377b4b311e45e270d984d..694d6b1eea937ed4bc6c4a154ec3d22d71dcac20 100644 --- a/api/version_vars.go +++ b/api/version_vars.go @@ -1,10 +1,10 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2021-10-07 12:02:04.067615 -0500 CDT m=+0.027463409 +// 2021-11-11 13:19:35.619263 -0600 CST m=+0.049820030 package api -const GITVERSION = `86c66226 update deps` -const SEMVER = "2.10.0" +const GITVERSION = `e595b772 Merge branch 'hotfix/stale-registration' into 'release'` +const SEMVER = "3.2.0" const DEPENDENCIES = `module gitlab.com/elixxir/client go 1.13 @@ -24,18 +24,18 @@ 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.20211006231929-cc2735ca43a6 - gitlab.com/elixxir/crypto v0.0.7-0.20211006231624-44434504fff4 + gitlab.com/elixxir/comms v0.0.4-0.20211101174956-590ba1b47887 + gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c gitlab.com/elixxir/ekv v0.1.5 - gitlab.com/elixxir/primitives v0.0.3-0.20210920180121-b85bca5212f4 - gitlab.com/xx_network/comms v0.0.4-0.20211006231434-99dd38f025a7 - gitlab.com/xx_network/crypto v0.0.5-0.20211006222352-8e0ac37b86b0 - gitlab.com/xx_network/primitives v0.0.4-0.20210915220237-70cb4551d6f3 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + gitlab.com/elixxir/primitives v0.0.3-0.20211102233208-a716d5c670b6 + gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae + gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 + gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect google.golang.org/grpc v1.38.0 - google.golang.org/protobuf v1.26.0 + google.golang.org/protobuf v1.27.1 gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) 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 2df75cdd1519618135db887d28cef16d14a12b29..729aac8781d24258e61a077b01ee4eb5a43baec6 100644 --- a/bindings/group.go +++ b/bindings/group.go @@ -8,11 +8,14 @@ package bindings import ( + "encoding/json" + "fmt" "github.com/pkg/errors" 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. @@ -61,9 +64,13 @@ func NewGroupManager(client *Client, requestFunc GroupRequestFunc, // 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 { grp, rounds, status, err := g.m.MakeGroup(membership.list, name, message) - return &NewGroupReport{&Group{grp}, rounds, status}, err + errStr := "" + if err !=nil{ + errStr = err.Error() + } + return &NewGroupReport{&Group{grp}, rounds, status, errStr} } // ResendRequest resends a group request to all members in the group. The rounds @@ -75,9 +82,18 @@ func (g *GroupChat) ResendRequest(groupIdBytes []byte) (*NewGroupReport, error) errors.Errorf("Failed to unmarshal group ID: %+v", err) } + grp, exists := g.m.GetGroup(groupID) + if !exists{ + return nil,errors.Errorf("Failed to find group %s", groupID) + } + rounds, status, err := g.m.ResendRequest(groupID) - return &NewGroupReport{&Group{}, rounds, status}, nil + errStr := "" + if err !=nil{ + errStr = err.Error() + } + return &NewGroupReport{&Group{grp}, rounds, status, errStr}, nil } // JoinGroup allows a user to join a group when they receive a request. The @@ -102,14 +118,14 @@ 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 @@ -139,6 +155,10 @@ 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. @@ -146,6 +166,13 @@ type NewGroupReport struct { group *Group rounds []id.Round status gc.RequestStatus + err string +} + +type GroupReportDisk struct { + List []id.Round + GrpId []byte + Status int } // GetGroup returns the Group. @@ -161,13 +188,76 @@ func (ngr *NewGroupReport) GetRoundList() *RoundList { // GetStatus returns the status of the requests sent when creating a new group. // status = 0 an error occurred before any requests could be sent -// 1 all requests failed to send -// 2 some request failed and some succeeded -// 3, all requests sent successfully +// 1 all requests failed to send (call Resend Group) +// 2 some request failed and some succeeded (call Resend Group) +// 3, all requests sent successfully (call Resend Group) func (ngr *NewGroupReport) GetStatus() int { return int(ngr.status) } +// GetError returns the string of an error. +// Will be an empty string if no error occured +func (ngr *NewGroupReport) GetError() string { + return ngr.err +} + + +func (ngr *NewGroupReport) Marshal() ([]byte, error) { + grpReportDisk := GroupReportDisk{ + List: ngr.rounds, + GrpId: ngr.group.GetID()[:], + Status: ngr.GetStatus(), + } + return json.Marshal(&grpReportDisk) +} + +func (ngr *NewGroupReport) Unmarshal(b []byte) error { + grpReportDisk := GroupReportDisk{} + if err := json.Unmarshal(b, &grpReportDisk); err != nil { + return errors.New(fmt.Sprintf("Failed to unmarshal group "+ + "report: %s", err.Error())) + } + + grpId, err := id.Unmarshal(grpReportDisk.GrpId) + if err != nil { + return errors.New(fmt.Sprintf("Failed to unmarshal group "+ + "id: %s", err.Error())) + } + + ngr.group.g.ID = grpId + ngr.rounds = grpReportDisk.List + ngr.status = gc.RequestStatus(grpReportDisk.Status) + + return nil +} + +//// +// 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) + return int64(ts) +} + //// // Group Structure //// @@ -188,6 +278,24 @@ 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. @@ -291,7 +399,8 @@ func (gmr *GroupMessageReceive) GetTimestampNano() int64 { // GetTimestampMS returns the message timestamp in milliseconds. func (gmr *GroupMessageReceive) GetTimestampMS() int64 { - return gmr.Timestamp.UnixNano() / 1_000_000 + ts := uint64(gmr.Timestamp.UnixNano()) / uint64(time.Millisecond) + return int64(ts) } // GetRoundID returns the ID of the round the message was sent on. @@ -308,5 +417,6 @@ func (gmr *GroupMessageReceive) GetRoundTimestampNano() int64 { // GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the // message was sent on. func (gmr *GroupMessageReceive) GetRoundTimestampMS() int64 { - return gmr.RoundTimestamp.UnixNano() / 1_000_000 + ts := uint64(gmr.RoundTimestamp.UnixNano()) / uint64(time.Millisecond) + return int64(ts) } 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..3117369675d4ec73c1454446bf056645be77c96d --- /dev/null +++ b/bindings/preimage.go @@ -0,0 +1,54 @@ +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 +} + +// hack on getPreimages so it works on iOS per https://github.com/golang/go/issues/46893 +func (c *Client) GetPreimagesHack(dummy string, 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 036ff66cf9fef36c45b79b114ce85a5de5774b59..a2c9db19265e702aec3c5d268185cd2156b3b8dd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,6 +23,7 @@ import ( "gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/crypto/contact" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/utils" "io/ioutil" "log" "os" @@ -236,7 +237,7 @@ var rootCmd = &cobra.Command{ expectedCnt := viper.GetUint("receiveCount") receiveCnt := uint(0) waitSecs := viper.GetUint("waitTimeout") - waitTimeout := time.Duration(waitSecs)* time.Second + waitTimeout := time.Duration(waitSecs) * time.Second done := false for !done && expectedCnt != 0 { @@ -261,7 +262,7 @@ var rootCmd = &cobra.Command{ //wait an extra 5 seconds to make sure no messages were missed done = false - timer := time.NewTimer(5*time.Second) + timer := time.NewTimer(5 * time.Second) for !done { select { case <-timer.C: @@ -382,6 +383,8 @@ func createClient() *api.Client { regCode := viper.GetString("regcode") precannedID := viper.GetUint("sendid") userIDprefix := viper.GetString("userid-prefix") + protoUserPath := viper.GetString("protoUserPath") + //create a new client if none exist if _, err := os.Stat(storeDir); os.IsNotExist(err) { // Load NDF @@ -394,14 +397,19 @@ func createClient() *api.Client { if precannedID != 0 { err = api.NewPrecannedClient(precannedID, string(ndfJSON), storeDir, []byte(pass)) - } else { - if userIDprefix != "" { - err = api.NewVanityClient(string(ndfJSON), storeDir, - []byte(pass), regCode, userIDprefix) - } else { - err = api.NewClient(string(ndfJSON), storeDir, - []byte(pass), regCode) + } else if protoUserPath != "" { + protoUserJson, err := utils.ReadFile(protoUserPath) + if err != nil { + jww.FATAL.Panicf("%v", err) } + err = api.NewProtoClient_Unsafe(string(ndfJSON), storeDir, + []byte(pass), protoUserJson) + } else if userIDprefix != "" { + err = api.NewVanityClient(string(ndfJSON), storeDir, + []byte(pass), regCode, userIDprefix) + } else { + err = api.NewClient(string(ndfJSON), storeDir, + []byte(pass), regCode) } if err != nil { @@ -431,7 +439,7 @@ func initClient() *api.Client { pass := viper.GetString("password") storeDir := viper.GetString("session") - + jww.DEBUG.Printf("sessionDur: %v", storeDir) netParams := params.GetDefaultNetwork() netParams.E2EParams.MinKeys = uint16(viper.GetUint("e2eMinKeys")) netParams.E2EParams.MaxKeys = uint16(viper.GetUint("e2eMaxKeys")) @@ -453,6 +461,20 @@ func initClient() *api.Client { jww.FATAL.Panicf("%+v", err) } + if protoUser := viper.GetString("protoUserOut"); protoUser != "" { + + jsonBytes, err := client.ConstructProtoUerFile() + if err != nil { + jww.FATAL.Panicf("Failed to construct proto user file: %v", err) + } + + err = utils.WriteFileDef(protoUser, jsonBytes) + if err != nil { + jww.FATAL.Panicf("Failed to write proto user to file: %v", err) + } + + } + return client } @@ -823,30 +845,30 @@ func init() { rootCmd.Flags().UintP("receiveCount", "", 1, "How many messages we should wait for before quitting") viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount")) - rootCmd.Flags().UintP("waitTimeout", "", 15, + rootCmd.PersistentFlags().UintP("waitTimeout", "", 15, "The number of seconds to wait for messages to arrive") viper.BindPFlag("waitTimeout", - rootCmd.Flags().Lookup("waitTimeout")) + rootCmd.PersistentFlags().Lookup("waitTimeout")) rootCmd.Flags().BoolP("unsafe", "", false, "Send raw, unsafe messages without e2e encryption.") viper.BindPFlag("unsafe", rootCmd.Flags().Lookup("unsafe")) - rootCmd.Flags().BoolP("unsafe-channel-creation", "", false, + rootCmd.PersistentFlags().BoolP("unsafe-channel-creation", "", false, "Turns off the user identity authenticated channel check, "+ "automatically approving authenticated channels") viper.BindPFlag("unsafe-channel-creation", - rootCmd.Flags().Lookup("unsafe-channel-creation")) + rootCmd.PersistentFlags().Lookup("unsafe-channel-creation")) rootCmd.Flags().BoolP("accept-channel", "", false, "Accept the channel request for the corresponding recipient ID") viper.BindPFlag("accept-channel", rootCmd.Flags().Lookup("accept-channel")) - rootCmd.Flags().Bool("delete-channel", false, + rootCmd.PersistentFlags().Bool("delete-channel", false, "Delete the channel information for the corresponding recipient ID") viper.BindPFlag("delete-channel", - rootCmd.Flags().Lookup("delete-channel")) + rootCmd.PersistentFlags().Lookup("delete-channel")) rootCmd.Flags().BoolP("send-auth-request", "", false, "Send an auth request to the specified destination and wait"+ @@ -893,6 +915,17 @@ func init() { rootCmd.Flags().String("profile-cpu", "", "Enable cpu profiling to this file") viper.BindPFlag("profile-cpu", rootCmd.Flags().Lookup("profile-cpu")) + + // Proto user flags + rootCmd.Flags().String("protoUserPath", "", + "Path to proto user JSON file containing cryptographic primitives "+ + "the client will load") + viper.BindPFlag("protoUserPath", rootCmd.Flags().Lookup("protoUserPath")) + rootCmd.Flags().String("protoUserOut", "", + "Path to which a normally constructed client "+ + "will write proto user JSON file") + viper.BindPFlag("protoUserOut", rootCmd.Flags().Lookup("protoUserOut")) + } // initConfig reads in config file and ENV variables if set. 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/cmd/version.go b/cmd/version.go index 51a4bb42caeb6f5ac9da3a38fb47eceabc6faf7f..e9a3316a3f579f281100f728ff96ca3a1f24b7e8 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,7 +18,7 @@ import ( ) // Change this value to set the version for this build -const currentVersion = "2.10.0" +const currentVersion = "3.2.0" func Version() string { out := fmt.Sprintf("Elixxir Client v%s -- %s\n\n", api.SEMVER, 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 dbe4d79a58accf98ee34cc3b003b82eb7dcac69e..68b97ffd9c304c2e89affdbf7a8add44d2c1b344 100644 --- a/go.mod +++ b/go.mod @@ -17,10 +17,10 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 - gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e + gitlab.com/elixxir/comms v0.0.4-0.20211101174956-590ba1b47887 + gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c gitlab.com/elixxir/ekv v0.1.5 - gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 + gitlab.com/elixxir/primitives v0.0.3-0.20211102233208-a716d5c670b6 gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb @@ -28,7 +28,7 @@ require ( golang.org/x/net v0.0.0-20210525063256-abc453219eb5 google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect google.golang.org/grpc v1.38.0 - google.golang.org/protobuf v1.26.0 + google.golang.org/protobuf v1.27.1 gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 37d23fd0792d608122530d1b2d168f77b572f356..ac363b511e9b8e883b2d9431aa16e6e937535941 100644 --- a/go.sum +++ b/go.sum @@ -253,22 +253,21 @@ 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.20211101174956-590ba1b47887 h1:SOQaoEvc6RqImz86jSjsj7wIW3ZhgxXc38GzvRkKdOw= +gitlab.com/elixxir/comms v0.0.4-0.20211101174956-590ba1b47887/go.mod h1:rQpTeFVSn08ocbQeEw5AbMhGWXHfXmQ0y1/ZprAIVVU= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d h1:tI3YYoHVb/KViRhagzQM0XKdw0hJ7KcuSQXFIWhmtSE= -gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= -gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e h1:JCYcXV9GBvOVRfYhm1e2b52AnvPQrEQLmOR9XbjzE8k= -gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= +gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c h1:HIr2HBhZqSAKdPRBdEY0/qravISL619O2yuTY/DQTdo= +gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c/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= gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8= gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 h1:sXX3/hewV4TQLxT2iKBfnfgW/A1eXoEfv5raJxTb79s= gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576/go.mod h1:zZy8AlOISFm5IG4G4sylypnz7xNBfZ5mpXiibqJT8+8= +gitlab.com/elixxir/primitives v0.0.3-0.20211102233208-a716d5c670b6 h1:ymWyFBFLcRQiuSId54dq8PVeiV4W7a9737kV46Thjlk= +gitlab.com/elixxir/primitives v0.0.3-0.20211102233208-a716d5c670b6/go.mod h1:zZy8AlOISFm5IG4G4sylypnz7xNBfZ5mpXiibqJT8+8= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae h1:jmZWmSm8eH40SX5B5uOw2XaYoHYqVn8daTfa6B80AOs= gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae/go.mod h1:wR9Vx0KZLrIs0g2Efcp0UwFPStjcDRWkg/DJLVQI2vw= @@ -447,8 +446,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 2c277b4384a04e187e722488d43b99357f1eb0e7..7cfe7dfd8326b1f221b9ef4a9bf16c8096879c3a 100644 --- a/groupChat/makeGroup.go +++ b/groupChat/makeGroup.go @@ -11,10 +11,13 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" gs "gitlab.com/elixxir/client/groupChat/groupStore" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/group" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" "strconv" ) @@ -74,25 +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 members %s", - g.Name, g.ID, g.Members) + 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 6691e54154e613678d3b50684aa333a63b9c1f64..4b0b3066772f7f7e72e38ef531c32f7d44b3f797 100644 --- a/groupChat/manager.go +++ b/groupChat/manager.go @@ -14,8 +14,10 @@ import ( gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/fastRNG" @@ -79,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) } @@ -128,7 +131,14 @@ func (m Manager) JoinGroup(g gs.Group) error { return errors.Errorf(joinGroupErr, g.ID, err) } - jww.DEBUG.Printf("Joined group %s.", g.ID) + 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 } @@ -139,9 +149,16 @@ func (m Manager) LeaveGroup(groupID *id.ID) error { return errors.Errorf(leaveGroupErr, groupID, err) } - jww.DEBUG.Printf("Left group %s.", groupID) + edgeStore := m.store.GetEdge() + err := edgeStore.Remove(edge.Preimage{ + Data: groupID[:], + Type: preimage.Group, + Source: groupID[:], + }, m.store.GetUser().ReceptionID) - return nil + jww.DEBUG.Printf("Left group with ID %s.", groupID) + + return err } // GetGroups returns a list of all registered groupChat IDs. 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 cdb40888ee41c3489fe23f026c1f7e5d05f3caf6..916410d1759add008948cd8d748c0bcf47cd9add 100644 --- a/groupChat/send.go +++ b/groupChat/send.go @@ -36,41 +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) } - jww.DEBUG.Printf("Sent message to group %s.", groupID) + jww.DEBUG.Printf("Sent message to %d members in group %s at %s.", + len(messages), groupID, timeNow) - return rid, nil + 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) @@ -125,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 @@ -152,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]) @@ -174,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 cd7913fcf9488d1a5927d4171f7677e99cf245c6..edcecc77f1b5516a9ed75d9481738210374b88e4 100644 --- a/groupChat/sendRequests.go +++ b/groupChat/sendRequests.go @@ -50,6 +50,7 @@ func (m Manager) sendRequests(g gs.Group) ([]id.Round, RequestStatus, error) { KeyPreimage: g.KeyPreimage.Bytes(), Members: g.Members.Serialize(), Message: g.InitMessage, + Created: g.Created.UnixNano(), }) if err != nil { return nil, NotSent, errors.Errorf(protoMarshalErr, err) @@ -101,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..476e6b7157c8953ac82a572e119f725c0a081475 --- /dev/null +++ b/interfaces/preimage/generate.go @@ -0,0 +1,30 @@ +package preimage + +import ( + "gitlab.com/xx_network/primitives/id" + "golang.org/x/crypto/blake2b" +) + +func Generate(data []byte, t string) []byte { + + if t==Default{ + return data + } + // 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..0eb0689362e92e1f3145572f2551a45448adef54 --- /dev/null +++ b/interfaces/preimage/request.go @@ -0,0 +1,20 @@ +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 { + // Base 64 encode hash and truncate + return uid[:] +} 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/interfaces/user/proto.go b/interfaces/user/proto.go new file mode 100644 index 0000000000000000000000000000000000000000..6b9cf1e3a6b338a08abd386b92772314b5f499e3 --- /dev/null +++ b/interfaces/user/proto.go @@ -0,0 +1,33 @@ +package user + +import ( + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" +) + +type Proto struct { + //General Identity + TransmissionID *id.ID + TransmissionSalt []byte + TransmissionRSA *rsa.PrivateKey + ReceptionID *id.ID + ReceptionSalt []byte + ReceptionRSA *rsa.PrivateKey + Precanned bool + // Timestamp in which user has registered with the network + RegistrationTimestamp int64 + + RegCode string + + TransmissionRegValidationSig []byte + ReceptionRegValidationSig []byte + + //cmix Identity + CmixDhPrivateKey *cyclic.Int + CmixDhPublicKey *cyclic.Int + + //e2e Identity + E2eDhPrivateKey *cyclic.Int + E2eDhPublicKey *cyclic.Int +} diff --git a/interfaces/user/user.go b/interfaces/user/user.go index 8788856b494cf386dbd903510680935f2b320daa..56f260d03527ce2c605c5fe781f3be3935cc106c 100644 --- a/interfaces/user/user.go +++ b/interfaces/user/user.go @@ -13,7 +13,6 @@ import ( "gitlab.com/elixxir/primitives/fact" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" - "time" ) type User struct { @@ -26,7 +25,7 @@ type User struct { ReceptionRSA *rsa.PrivateKey Precanned bool // Timestamp in which user has registered with the network - RegistrationTimestamp time.Time + RegistrationTimestamp int64 //cmix Identity CmixDhPrivateKey *cyclic.Int @@ -44,3 +43,20 @@ func (u User) GetContact() contact.Contact { Facts: make([]fact.Fact, 0), } } + +func NewUserFromProto(proto *Proto) User { + return User{ + TransmissionID: proto.TransmissionID, + TransmissionSalt: proto.TransmissionSalt, + TransmissionRSA: proto.TransmissionRSA, + ReceptionID: proto.ReceptionID, + ReceptionSalt: proto.ReceptionSalt, + ReceptionRSA: proto.ReceptionRSA, + Precanned: proto.Precanned, + RegistrationTimestamp: proto.RegistrationTimestamp, + CmixDhPrivateKey: proto.CmixDhPrivateKey, + CmixDhPublicKey: proto.CmixDhPublicKey, + E2eDhPrivateKey: proto.E2eDhPrivateKey, + E2eDhPublicKey: proto.E2eDhPublicKey, + } +} 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..50f98f0aca1371723361d838942a60b611ef575a 100644 --- a/network/follow.go +++ b/network/follow.go @@ -29,6 +29,7 @@ import ( "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/network/rounds" "gitlab.com/elixxir/client/stoppable" + rounds2 "gitlab.com/elixxir/client/storage/rounds" pb "gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/primitives/knownRounds" "gitlab.com/elixxir/primitives/states" @@ -110,6 +111,16 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, "impossible: %+v", err) } + // While polling with a fake identity, it is necessary to have + // populated earliestRound data. However, as with fake identities + // we want the values to be randomly generated rather than based on + // actual state. + if identity.Fake { + fakeEr := &rounds2.EarliestRound{} + fakeEr.Set(m.GetFakeEarliestRound()) + identity.ER = fakeEr + } + atomic.AddUint64(m.tracker, 1) // Get client version for poll @@ -126,6 +137,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, EndTimestamp: identity.EndValid.UnixNano(), ClientVersion: []byte(version.String()), FastPolling: m.param.FastPolling, + LastRound: uint64(identity.ER.Get()), } result, err := m.GetSender().SendToAny(func(host *connect.Host) (interface{}, error) { @@ -305,6 +317,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, // move the earliest unknown round tracker forward to the earliest // tracked round if it is behind earliestTrackedRound := id.Round(pollResp.EarliestRound) + m.SetFakeEarliestRound(earliestTrackedRound) updated, old, _ := identity.ER.Set(earliestTrackedRound) if old == 0 { if gwRoundsState.GetLastChecked() > id.Round(m.param.KnownRoundsThreshold) { @@ -321,11 +334,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/sender.go b/network/gateway/sender.go index 9d2f3d3ad9fa96f3dee1ea987ef4e5b9f55635dd..c3c7bff8259feb2f477d658791c39c42e079064c 100644 --- a/network/gateway/sender.go +++ b/network/gateway/sender.go @@ -53,7 +53,8 @@ func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error // Retry of the proxy could not communicate jww.INFO.Printf("Unable to SendToAny via %s: non-fatal error received, retrying: %s", proxies[proxy].GetId().String(), err) - } else if strings.Contains(err.Error(), "unable to connect to target host") { + } else if strings.Contains(err.Error(), "unable to connect to target host") || + strings.Contains(err.Error(), "unable to find target host") { // Retry of the proxy could not communicate jww.WARN.Printf("Unable to SendToAny via %s: %s,"+ " proxy could not contact requested host", @@ -94,7 +95,8 @@ func (s *Sender) SendToPreferred(targets []*id.ID, // Retry of the proxy could not communicate jww.INFO.Printf("Unable to to SendToPreferred first pass %s via %s: non-fatal error received, retrying: %s", targets[i], targetHosts[i].GetId(), err) - } else if strings.Contains(err.Error(), "unable to connect to target host") { + } else if strings.Contains(err.Error(), "unable to connect to target host") || + strings.Contains(err.Error(), "unable to find target host") { // Retry of the proxy could not communicate jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, "+ "proxy could not contact requested host", @@ -157,7 +159,8 @@ func (s *Sender) SendToPreferred(targets []*id.ID, // Retry of the proxy could not communicate jww.INFO.Printf("Unable to SendToPreferred second pass %s via %s: non-fatal error received, retrying: %s", target, proxy, err) - } else if strings.Contains(err.Error(), "unable to connect to target host") { + } else if strings.Contains(err.Error(), "unable to connect to target host") || + strings.Contains(err.Error(), "unable to find target host") { // Retry of the proxy could not communicate jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s: %s,"+ " proxy could not contact requested host", diff --git a/network/manager.go b/network/manager.go index 35f3c668247586618ea3d52e442b3800bc2efd76..bb69e30a5154ec09e4d029d6c797171d088e01fb 100644 --- a/network/manager.go +++ b/network/manager.go @@ -11,8 +11,11 @@ package network // and intraclient state are accessible through the context object. import ( + "crypto/rand" + "encoding/binary" "fmt" "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/network/ephemeral" @@ -28,11 +31,20 @@ import ( "gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/comms/network" "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" "math" + "sync/atomic" "time" ) +// fakeIdentityRange indicates the range generated between +// 0 (most current) and fakeIdentityRange rounds behind the earliest known +// round that will be used as the earliest round when polling with a +// fake identity. +const fakeIdentityRange = 800 + // Manager implements the NetworkManager interface inside context. It // controls access to network resources and implements all the communications // functions used by the client. @@ -49,6 +61,9 @@ type manager struct { round *rounds.Manager message *message.Manager + // Earliest tracked round + earliestRound *uint64 + //number of polls done in a period of time tracker *uint64 latencySum uint64 @@ -81,13 +96,14 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, session.E2e().SetE2ESessionParams(params.E2EParams) tracker := uint64(0) - + earliest := uint64(0) // create manager object m := manager{ param: params, tracker: &tracker, addrSpace: ephemeral.NewAddressSpace(), events: events, + earliestRound: &earliest, } if params.VerboseRoundTracking { @@ -241,3 +257,22 @@ func (m *manager) GetVerboseRounds() string { } return m.verboseRounds.String() } + + +func (m *manager) SetFakeEarliestRound(rnd id.Round) { + atomic.StoreUint64(m.earliestRound, uint64(rnd)) +} + +// GetFakeEarliestRound generates a random earliest round for a fake identity. +func (m *manager) GetFakeEarliestRound() id.Round { + b, err := csprng.Generate(8, rand.Reader) + if err != nil { + jww.FATAL.Panicf("Could not get random number: %v", err) + } + + rangeVal := binary.LittleEndian.Uint64(b) % 800 + + earliestKnown := atomic.LoadUint64(m.earliestRound) + + return id.Round(earliestKnown - rangeVal) +} 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..b889b0ad86a7a09e7df10ab1ea72bd03541a4057 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..31e9ef78ccc7baea47909d687a2b0671994511e4 100644 --- a/network/message/sendManyCmix.go +++ b/network/message/sendManyCmix.go @@ -103,6 +103,7 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, firstGateway, roundKeys, err := processRound(instance, session, nodeRegistration, bestRound, recipientString, msgDigests) if err != nil { + jww.INFO.Printf("error processing round: %v", err) jww.WARN.Printf("SendManyCMIX failed to process round %d "+ "(will retry): %+v", bestRound.ID, err) continue @@ -115,8 +116,9 @@ 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 { + jww.INFO.Printf("error building slot received: %v", err) return 0, []ephemeral.Id{}, errors.Errorf("failed to build "+ "slot message for %s: %+v", recipient, err) } @@ -148,7 +150,8 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipientString, bestRound, err) return result, errors.WithMessagef(err, - "SendManyCMIX %s", unrecoverableError) + "SendManyCMIX %s (via %s): %s", + target, host, unrecoverableError) } return result, err @@ -161,9 +164,10 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message, jww.ERROR.Printf("SendManyCMIX failed to send to EphIDs [%s] "+ "(sources: %s) on round %d, trying a new round %+v", ephemeralIdsString, recipientString, bestRound.ID, err) + jww.INFO.Printf("error received, continuing: %v", err) continue } - + jww.INFO.Printf("error received: %v", err) return 0, []ephemeral.Id{}, err } diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go index a978beeae77c2fffa723c15f495ebf1fa4925c5c..68119395dc81f3c3dff50dfcfdd5b5d8b6da9ea4 100644 --- a/storage/e2e/manager.go +++ b/storage/e2e/manager.go @@ -14,13 +14,13 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/storage/utility" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" dh "gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/xx_network/primitives/id" "golang.org/x/crypto/blake2b" - "sort" ) const managerPrefix = "Manager{partner:%s}" @@ -228,10 +228,22 @@ const relationshipFpLength = 15 // relationship. The fingerprint is a base 64 encoded hash of of the two // relationship fingerprints truncated to 15 characters. func (m *Manager) GetRelationshipFingerprint() string { + + // Base 64 encode hash and truncate + return base64.StdEncoding.EncodeToString(m.GetRelationshipFingerprintBytes())[:relationshipFpLength] +} + +// GetRelationshipFingerprintBytes returns a unique fingerprint for an E2E +// relationship. used for the e2e preimage. +func (m *Manager) GetRelationshipFingerprintBytes() []byte { // Sort fingerprints - fps := [][]byte{m.receive.fingerprint, m.send.fingerprint} - less := func(i, j int) bool { return bytes.Compare(fps[i], fps[j]) == -1 } - sort.Slice(fps, less) + var fps [][]byte + + if bytes.Compare(m.receive.fingerprint, m.send.fingerprint) == 1 { + fps = [][]byte{m.send.fingerprint, m.receive.fingerprint} + } else { + fps = [][]byte{m.receive.fingerprint, m.send.fingerprint} + } // Hash fingerprints h, _ := blake2b.New256(nil) @@ -240,5 +252,17 @@ func (m *Manager) GetRelationshipFingerprint() string { } // Base 64 encode hash and truncate - return base64.StdEncoding.EncodeToString(h.Sum(nil))[:relationshipFpLength] + return h.Sum(nil) +} + +// GetE2EPreimage returns a hash of the unique +// fingerprint for an E2E relationship message. +func (m *Manager) GetE2EPreimage() []byte { + return preimage.Generate(m.GetRelationshipFingerprintBytes(), preimage.E2e) +} + +// GetRekeyPreimage returns a hash of the unique +// fingerprint for an E2E rekey message. +func (m *Manager) GetRekeyPreimage() []byte { + return preimage.Generate(m.GetRelationshipFingerprintBytes(), preimage.Rekey) } diff --git a/storage/edge/edge.go b/storage/edge/edge.go new file mode 100644 index 0000000000000000000000000000000000000000..a720055d73f95dcb8c70455cb16a40a3ba0f81b9 --- /dev/null +++ b/storage/edge/edge.go @@ -0,0 +1,266 @@ +package edge + +import ( + "encoding/json" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage/versioned" + fingerprint2 "gitlab.com/elixxir/crypto/fingerprint" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" + "sync" +) + +// This stores Preimages which can be used with the identity fingerprint system. + +const ( + edgeStorePrefix = "edgeStore" + edgeStoreKey = "edgeStoreKey" + edgeStoreVersion = 0 +) + +type ListUpdateCallBack func(identity *id.ID, deleted bool) + +type Store struct { + kv *versioned.KV + edge map[id.ID]Preimages + callbacks map[id.ID][]ListUpdateCallBack + mux sync.RWMutex +} + +// NewStore creates a new edge store object and inserts the default Preimages +// for the base identity. +func NewStore(kv *versioned.KV, baseIdentity *id.ID) (*Store, error) { + kv = kv.Prefix(edgeStorePrefix) + + s := &Store{ + kv: kv, + edge: make(map[id.ID]Preimages), + callbacks: make(map[id.ID][]ListUpdateCallBack), + } + + defaultPreimages := newPreimages(baseIdentity) + err := defaultPreimages.save(kv, baseIdentity) + if err != nil { + return nil, errors.WithMessage(err, "Failed to create preimage store, "+ + "failed to create default Preimages") + } + + s.edge[*baseIdentity] = defaultPreimages + + return s, s.save() +} + +// Add adds the Preimage to the list of the given identity and calls any +// associated callbacks. +func (s *Store) Add(preimage Preimage, identity *id.ID) { + s.mux.Lock() + defer s.mux.Unlock() + + // Get the list to update, create if needed + preimages, exists := s.edge[*identity] + if !exists { + preimages = newPreimages(identity) + } + + // Add to the list + if !preimages.add(preimage) { + return + } + + // Store the updated list + if err := preimages.save(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to store preimages list after adding "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + // Update the map + s.edge[*identity] = preimages + if !exists { + err := s.save() + if err != nil { + jww.FATAL.Panicf("Failed to store edge store after adding "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + } + + // Call any callbacks to notify + for _, cb := range s.callbacks[*identity] { + go cb(identity, false) + } + + return +} + +// Remove deletes the preimage for the given identity and triggers the +// associated callback. If the given preimage is the last in the Preimages list, +// then the entire list is removed and the associated callback will be triggered +// with the boolean indicating the list was deleted. +func (s *Store) Remove(preimage Preimage, identity *id.ID) error { + s.mux.Lock() + defer s.mux.Unlock() + + preimages, exists := s.edge[*identity] + if !exists { + return errors.Errorf("cannot delete preimage %v from identity %s; "+ + "identity cannot be found", preimage.Data, identity) + } + + preimages.remove(preimage.Data) + + if len(preimages) == 0 { + delete(s.edge, *identity) + if err := s.save(); err != nil { + jww.FATAL.Panicf("Failed to store edge store after removing "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + if err := preimages.delete(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to delete preimage list store after "+ + "removing preimage %v to identity %s: %+v", preimage.Data, + identity, err) + } + + // Call any callbacks to notify + for i := range s.callbacks[*identity] { + cb := s.callbacks[*identity][i] + go cb(identity, true) + } + + return nil + } + + if err := preimages.save(s.kv, identity); err != nil { + jww.FATAL.Panicf("Failed to store preimage list store after removing "+ + "preimage %v to identity %s: %+v", preimage.Data, identity, err) + } + + s.edge[*identity] = preimages + + // Call any callbacks to notify + for i := range s.callbacks[*identity] { + cb := s.callbacks[*identity][i] + go cb(identity, false) + } + + return nil +} + +// Get returns the Preimages list for the given identity. +func (s *Store) Get(identity *id.ID) ([]Preimage, bool) { + s.mux.RLock() + defer s.mux.RUnlock() + + preimages, exists := s.edge[*identity] + if !exists { + return nil, false + } + + preiamgesSlice := make([]Preimage, 0, len(preimages)) + + for _, preimage := range preimages { + preiamgesSlice = append(preiamgesSlice, preimage) + } + return preiamgesSlice, exists +} + +// Check looks checks if the identity fingerprint matches for any of +// the stored preimages. It returns the preimage it hit with if it +// finds one. +func (s *Store) Check(identity *id.ID, identityFP []byte, messageContents []byte) (bool, bool, Preimage) { + s.mux.RLock() + defer s.mux.RUnlock() + + preimages, exists := s.edge[*identity] + if !exists { + return false, false, Preimage{} + } + + for _, preimage := range preimages { + jww.INFO.Printf("checking ifp: %v, msg: %v, preimage %v", identityFP, messageContents, preimage) + if fingerprint2.CheckIdentityFP(identityFP, messageContents, preimage.Data) { + return true, true, preimage + } + } + + return true, false, Preimage{} +} + +// AddUpdateCallback adds the callback to be called for changes to the identity. +func (s *Store) AddUpdateCallback(identity *id.ID, luCB ListUpdateCallBack) { + s.mux.Lock() + defer s.mux.Unlock() + + s.callbacks[*identity] = append(s.callbacks[*identity], luCB) +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +func LoadStore(kv *versioned.KV) (*Store, error) { + kv = kv.Prefix(edgeStorePrefix) + + // Load the list of identities with preimage lists + obj, err := kv.Get(edgeStoreKey, preimageStoreVersion) + if err != nil { + return nil, errors.WithMessagef(err, "failed to load edge store") + } + + identities := make([]id.ID, 0) + + err = json.Unmarshal(obj.Data, &identities) + if err != nil { + return nil, errors.WithMessagef(err, "failed to unmarshal edge store") + } + + s := &Store{ + kv: kv, + edge: make(map[id.ID]Preimages), + callbacks: make(map[id.ID][]ListUpdateCallBack), + } + + // Load the preimage lists for all identities + for i := range identities { + eid := &identities[i] + + preimages, err := loadPreimages(kv, eid) + if err != nil { + return nil, err + } + + s.edge[*eid] = preimages + } + + return s, nil +} + +func (s *Store) save() error { + identities := make([]id.ID, 0, len(s.edge)) + + for eid := range s.edge { + identities = append(identities, eid) + } + + // JSON marshal + data, err := json.Marshal(&identities) + if err != nil { + return errors.WithMessagef(err, "Failed to marshal edge list for "+ + "storage") + } + + // Construct versioning object + obj := versioned.Object{ + Version: edgeStoreVersion, + Timestamp: netTime.Now(), + Data: data, + } + + // Save to storage + err = s.kv.Set(edgeStoreKey, preimageStoreVersion, &obj) + if err != nil { + return errors.WithMessagef(err, "Failed to store edge list") + } + + return nil +} diff --git a/storage/edge/edge_test.go b/storage/edge/edge_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c7823245f47a29d839622a63200ea46dfcba9185 --- /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.MakeDefault(identities[0]), 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.MakeDefault(identities[1]), 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) != 2 { + 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) != 0 { + t.Errorf("Length of preimages for identity %s inocrrect."+ + "\nexpected: %d\nreceived: %d", identities[1], 0, 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.MakeDefault(identities[0]), 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.MakeDefault(identities[1]), 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.MakeDefault(identities[0]), preimage.Default, identities[0].Bytes()}, + preimages[0], + preimages[2], + }, + { + Preimage{preimage.MakeDefault(identities[1]), preimage.Default, identities[1].Bytes()}, + preimages[1], + }, + } + + for i, identity := range identities { + pis, exists := receivedStore.Get(identity) + if !exists { + t.Errorf("Identity %s does not exist in loaded store", identity) + } + + if len(expectedPis[i]) != len(pis) { + t.Errorf("First Preimages for identity %s does not match expected, difrent lengths of %d and %d"+ + "\nexpected: %+v\nreceived: %+v", identities[0], len(expectedPis[i]), len(pis), expectedPis[i], pis) + } + + top: + for idx, lookup := range expectedPis[i] { + for _, checked := range pis { + if reflect.DeepEqual(lookup, checked) { + continue top + } + } + t.Errorf("Entree %d in expected %v not found in received %v", idx, lookup, pis) + } + + } +} + +func TestStore_Check(t *testing.T) { + // Initialize store + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + // Add preimages + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + s.Add(preimages[2], identities[0]) + + testMsg := []byte("test message 123") + preImageData := preimages[0].Data + testFp := fingerprint2.IdentityFP(testMsg, preImageData) + + has, forMe, receivedPreImage := s.Check(identities[0], testFp, testMsg) + + if !has || !forMe || !reflect.DeepEqual(receivedPreImage, preimages[0]) { + t.Errorf("Unexpected result from Check()."+ + "\nExpected results: (has: %v) "+ + "\n\t(forMe: %v)"+ + "\n\t(Preimage: %v)"+ + "\nReceived results: (has: %v) "+ + "\n\t(forME: %v)"+ + "\n\t(Preimage: %v)", true, true, preimages[0], + has, forMe, receivedPreImage) + } + + // Check with wrong identity (has should be true, for me false) + has, forMe, _ = s.Check(identities[1], testFp, testMsg) + if !has || forMe { + t.Errorf("Unexpected results from check."+ + "\nExpected results: (has: %v)"+ + "\n\t(ForMe %v)"+ + "\nReceived results: "+ + "has: %v"+ + "\n\t(ForMe: %v)", true, false, has, forMe) + } + +} + +func TestStore_save(t *testing.T) { + // Initialize store + s, _, _ := newTestStore(t) + identities := []*id.ID{ + id.NewIdFromString("identity0", id.User, t), + id.NewIdFromString("identity1", id.User, t), + } + preimages := []Preimage{ + {[]byte("ID0"), "default0", []byte("ID0")}, + {[]byte("ID1"), "default1", []byte("ID1")}, + {[]byte("ID2"), "default2", []byte("ID2")}, + } + + s.Add(preimages[0], identities[0]) + s.Add(preimages[1], identities[1]) + + // Save data to KV + err := s.save() + if err != nil { + t.Fatalf("save error: %v", err) + } + + // Manually pull from KV + vo, err := s.kv.Get(edgeStoreKey, preimageStoreVersion) + if err != nil { + t.Fatalf("Failed to retrieve from KV: %v", err) + } + + receivedIdentities := make([]id.ID, 0) + err = json.Unmarshal(vo.Data, &receivedIdentities) + if err != nil { + t.Fatalf("JSON unmarshal error: %v", err) + } + + for _, receivedId := range receivedIdentities { + _, exists := s.Get(&receivedId) + if !exists { + t.Fatalf("Identity retrieved from store does not match " + + "identity stored in") + } + } +} + +// newTestStore creates a new Store with a random base identity. Returns the +// Store, KV, and base identity. +func newTestStore(t *testing.T) (*Store, *versioned.KV, *id.ID) { + kv := versioned.NewKV(make(ekv.Memstore)) + baseIdentity, err := id.NewRandomID( + rand.New(rand.NewSource(time.Now().Unix())), id.User) + if err != nil { + t.Fatalf("Failed to generate random base identity: %+v", err) + } + + s, err := NewStore(kv, baseIdentity) + if err != nil { + t.Fatalf("Failed to create new test Store: %+v", err) + } + + return s, kv, baseIdentity +} diff --git a/storage/edge/preimage.go b/storage/edge/preimage.go new file mode 100644 index 0000000000000000000000000000000000000000..a8e4c89432dddc25596495fe69b59e7786ceab45 --- /dev/null +++ b/storage/edge/preimage.go @@ -0,0 +1,128 @@ +package edge + +import ( + "encoding/base64" + "encoding/json" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" +) + +const ( + preimageStoreKey = "preimageStoreKey" + preimageStoreVersion = 0 +) + +type Preimage struct { + Data []byte + Type string + Source []byte +} + +// key returns the key used to identify the Preimage in a map. +func (pi Preimage) key() string { + return base64.StdEncoding.EncodeToString(pi.Data) +} + +// Preimages is a map of unique Preimage keyed on their Data. +type Preimages map[string]Preimage + +// newPreimages makes a Preimages object for the given identity and populates +// it with the default preimage for the identity. Does not store to disk. +func newPreimages(identity *id.ID) Preimages { + defaultPreimage := Preimage{ + Data: preimage.MakeDefault(identity), + Type: preimage.Default, + Source: identity[:], + } + pis := Preimages{ + defaultPreimage.key(): defaultPreimage, + } + + return pis +} + +// add adds the preimage to the list. +func (pis Preimages) add(preimage Preimage) bool { + if _, exists := pis[preimage.key()]; exists { + return false + } + + pis[preimage.key()] = preimage + + return true +} + +// remove deletes the Preimage with the matching data from the list. +func (pis Preimages) remove(data []byte) { + key := base64.StdEncoding.EncodeToString(data) + delete(pis, key) +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +// loadPreimages loads a Preimages object for the given identity. +func loadPreimages(kv *versioned.KV, identity *id.ID) (Preimages, error) { + + // Get the data from storage + obj, err := kv.Get(preimagesKey(identity), preimageStoreVersion) + if err != nil { + return nil, errors.WithMessagef(err, "Failed to load edge Preimages "+ + "for identity %s", identity) + } + + var preimageList Preimages + err = json.Unmarshal(obj.Data, &preimageList) + if err != nil { + return nil, errors.WithMessagef(err, "failed to unmarshal edge "+ + "Preimages for identity %s", identity) + } + + return preimageList, nil +} + +// save stores the preimage list to disk. +func (pis Preimages) save(kv *versioned.KV, identity *id.ID) error { + // JSON marshal + data, err := json.Marshal(&pis) + if err != nil { + return errors.WithMessagef(err, "Failed to marshal Preimages list "+ + "for stroage for identity %s", identity) + } + + // Construct versioning object + obj := versioned.Object{ + Version: preimageStoreVersion, + Timestamp: netTime.Now(), + Data: data, + } + + // Save to storage + err = kv.Set(preimagesKey(identity), preimageStoreVersion, &obj) + if err != nil { + return errors.WithMessagef(err, "Failed to store Preimages list for "+ + "identity %s", identity) + } + + return nil +} + +// delete removes the Preimages from storage. +func (pis Preimages) delete(kv *versioned.KV, identity *id.ID) error { + err := kv.Delete(preimagesKey(identity), preimageStoreVersion) + if err != nil { + return errors.WithMessagef(err, "Failed to delete Preimages list for "+ + "identity %s", identity) + } + + return nil +} + +// preimagesKey generates the key for saving a Preimages to storage. +func preimagesKey(identity *id.ID) string { + return preimageStoreKey + ":" + identity.String() +} diff --git a/storage/edge/preimage_test.go b/storage/edge/preimage_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b31cc4274075344ad705b5308e7ac30e93bac5d0 --- /dev/null +++ b/storage/edge/preimage_test.go @@ -0,0 +1,246 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package edge + +import ( + "bytes" + "encoding/json" + "gitlab.com/elixxir/client/interfaces/preimage" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" +) + +// Tests that newPreimages returns the expected new Preimages. +func Test_newPreimages(t *testing.T) { + identity := id.NewIdFromString("identity", id.User, t) + pimg := Preimage{ + Data: preimage.MakeDefault(identity), + Type: "default", + Source: identity.Bytes(), + } + expected := Preimages{ + pimg.key(): pimg, + } + + received := newPreimages(identity) + + if !reflect.DeepEqual(expected, received) { + t.Errorf("New Preimages does not match expected."+ + "\nexpected: %+v\nreceived: %+v", expected, received) + } +} + +// Tests that Preimages.add adds the expected Preimage to the list. +func TestPreimages_add(t *testing.T) { + identity0 := id.NewIdFromString("identity0", id.User, t) + identity1 := id.NewIdFromString("identity1", id.User, t) + identity2 := id.NewIdFromString("identity3", id.User, t) + expected := Preimages{ + identity0.String(): {preimage.Generate(identity0.Bytes(), preimage.Default), preimage.Default, preimage.MakeDefault(identity0)}, + identity1.String(): {preimage.Generate(identity1.Bytes(), preimage.Group), preimage.Group, identity1.Bytes()}, + identity2.String(): {preimage.Generate(identity2.Bytes(), preimage.Default), preimage.Default, identity2.Bytes()}, + } + + pis := newPreimages(identity0) + preimageOne := Preimage{preimage.Generate(identity1.Bytes(), preimage.Group), preimage.Group, identity1.Bytes()} + exists := pis.add(preimageOne) + if !exists { + t.Errorf("Failed to add idenetity.") + } + + preimageTwo := Preimage{preimage.Generate(identity2.Bytes(), preimage.Default), preimage.Default, identity2.Bytes()} + exists = pis.add(preimageTwo) + if !exists { + t.Errorf("Failed to add idenetity.") + } + + for identity, pimg := range expected { + if _, exists = pis[pimg.key()]; !exists { + t.Errorf("Identity %s could not be found", identity) + } + } + + expectedPreimageIdentityTwo := Preimage{ + Data: preimage.Generate(identity2.Bytes(), preimage.Default), + Type: preimage.Default, + Source: identity2.Bytes(), + } + // Test that nothing happens when a Preimage with the same data exists + exists = pis.add(Preimage{preimage.Generate(identity2.Bytes(), preimage.Default), "test", identity2.Bytes()}) + if exists { + t.Errorf("Add idenetity that shoudl already exist.") + } + + receivedPreimageIdentityTwo := pis[preimageTwo.key()] + + if !reflect.DeepEqual(expectedPreimageIdentityTwo, receivedPreimageIdentityTwo) { + t.Errorf("Unexpected overwritting of existing identity") + } + +} + +// Tests that Preimages.remove removes all the correct Preimage from the list. +func TestPreimages_remove(t *testing.T) { + pis := make(Preimages) + var identities [][]byte + + // Add 10 Preimage to the list + for i := 0; i < 10; i++ { + identity := id.NewIdFromUInt(uint64(i), id.User, t) + pisType := preimage.Default + if i%2 == 0 { + pisType = preimage.Group + } + + exists := pis.add(Preimage{identity.Bytes(), pisType, identity.Bytes()}) + if !exists { + t.Errorf("Failed to add idenetity.") + } + identities = append(identities, identity.Bytes()) + } + + // Remove each Preimage, check if the length of the list has changed, and + // check that the correct Preimage was removed + for i, identity := range identities { + pis.remove(identity) + + if len(pis) != len(identities)-(i+1) { + t.Errorf("Length of Preimages incorrect after removing %d Premiages."+ + "\nexpected: %d\nreceived: %d", i, len(identities)-(i+1), + len(pis)) + } + + // Check if the correct Preimage was deleted + for _, pimg := range pis { + if bytes.Equal(pimg.Data, identity) { + t.Errorf("Failed to delete Preimage #%d: %+v", i, pimg) + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Storage Functions // +//////////////////////////////////////////////////////////////////////////////// + +// Tests that the Preimages loaded via loadPreimages matches the original saved +// to storage. +func Test_loadPreimages(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("Failed to save Preimages to storage: %+v", err) + } + + loaded, err := loadPreimages(kv, identity) + if err != nil { + t.Errorf("loadPreimages returned an error: %+v", err) + } + + if !reflect.DeepEqual(pis, loaded) { + t.Errorf("Loaded Preimages do not match original."+ + "\nexpected: %+v\nreceived: %+v", pis, loaded) + } +} + +// Tests that the data saved to storage via Preimages.save can be loaded and +// unmarshalled and that it matches the original. +func TestPreimages_save(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("save returned an error: %+v", err) + } + + obj, err := kv.Get(preimagesKey(identity), preimageStoreVersion) + if err != nil { + t.Errorf("Failed to load Preimages from storage: %+v", err) + } + + var loaded Preimages + err = json.Unmarshal(obj.Data, &loaded) + if err != nil { + t.Errorf("Failed to unmarshal Preimages loaded from storage: %+v", err) + } + + if !reflect.DeepEqual(pis, loaded) { + t.Errorf("Loaded Preimages do not match original."+ + "\nexpected: %+v\nreceived: %+v", pis, loaded) + } +} + +// Tests that Preimages.delete deletes the Preimages saved to storage by +// attempting to load them. +func TestPreimages_delete(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + identity := id.NewIdFromString("identity", id.User, t) + pis := Preimages{ + "a": {[]byte("identity0"), "default", []byte("identity0")}, + "b": {[]byte("identity0"), "group", []byte("identity0")}, + "c": {[]byte("identity1"), "default", []byte("identity1")}, + } + + err := pis.save(kv, identity) + if err != nil { + t.Errorf("Failed to save Preimages to storage: %+v", err) + } + + err = pis.delete(kv, identity) + if err != nil { + t.Errorf("delete returned an error: %+v", err) + } + + loaded, err := loadPreimages(kv, identity) + if err == nil { + t.Errorf("loadPreimages loaded a Preimages from storage when it "+ + "should have been deleted: %+v", loaded) + } +} + +// Consistency test: tests that preimagesKey returned the expected output for a +// set input. +func Test_preimagesKey(t *testing.T) { + expectedKeys := []string{ + "preimageStoreKey:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ACOG8m/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AEcN5N+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AGqU109DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AI4byb8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ALGivC7FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ANUprp6GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:APiwoQ5HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:ARw3k34IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + "preimageStoreKey:AT++he3JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", + } + + for i, expected := range expectedKeys { + identity := id.NewIdFromUInt(uint64(i)*1e16, id.User, t) + key := preimagesKey(identity) + if key != expected { + t.Errorf("Key #%d does not match expected."+ + "\nexpected: %q\nreceived: %q", i, expected, key) + } + } +} diff --git a/storage/session.go b/storage/session.go index fd98a85c22e1d1c3aea073ad3d1d9d6a709c6e1c..8ee2b43d3119b222612389685360d53b26bc9abf 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 @@ -92,7 +94,7 @@ func New(baseDir, password string, u userInterface.User, currentVersion version. s, err := initStore(baseDir, password) if err != nil { - return nil, errors.WithMessage(err, "Failed to create session") + return nil, errors.WithMessagef(err, "Failed to create session for %s", baseDir) } err = s.newRegStatus() @@ -101,7 +103,8 @@ func New(baseDir, password string, u userInterface.User, currentVersion version. "Create new session") } - s.user, err = user.NewUser(s.kv, u.TransmissionID, u.ReceptionID, u.TransmissionSalt, u.ReceptionSalt, u.TransmissionRSA, u.ReceptionRSA, u.Precanned) + s.user, err = user.NewUser(s.kv, u.TransmissionID, u.ReceptionID, u.TransmissionSalt, + u.ReceptionSalt, u.TransmissionRSA, u.ReceptionRSA, u.Precanned) if err != nil { return nil, errors.WithMessage(err, "Failed to create user") } @@ -154,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 } @@ -232,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 } @@ -314,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) @@ -417,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/storage/user.go b/storage/user.go index 975b574a2e9678a31eff7f0598b396ab6427a6af..313741471ee5e3fefa12349d2e9b441352b983e1 100644 --- a/storage/user.go +++ b/storage/user.go @@ -16,9 +16,9 @@ func (s *Session) GetUser() user.User { return user.User{ TransmissionID: ci.GetTransmissionID().DeepCopy(), TransmissionSalt: copySlice(ci.GetTransmissionSalt()), - TransmissionRSA: ci.GetReceptionRSA(), + TransmissionRSA: ci.GetTransmissionRSA(), ReceptionID: ci.GetReceptionID().DeepCopy(), - RegistrationTimestamp: s.user.GetRegistrationTimestamp(), + RegistrationTimestamp: s.user.GetRegistrationTimestamp().UnixNano(), ReceptionSalt: copySlice(ci.GetReceptionSalt()), ReceptionRSA: ci.GetReceptionRSA(), Precanned: ci.IsPrecanned(), diff --git a/ud/lookup.go b/ud/lookup.go index c5d8b4dd96757ec69b52e629ad69c131b92ed7aa..2dc2dd2a8724227423df2ff028809746aa49cc8f 100644 --- a/ud/lookup.go +++ b/ud/lookup.go @@ -23,9 +23,6 @@ type lookupCallback func(contact.Contact, error) // system or returns by the timeout. func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout) - if !m.IsRegistered() { - return errors.New("Failed to lookup: client is not registered.") - } // Build the request and marshal it request := &LookupSend{UserID: uid.Marshal()} diff --git a/ud/search.go b/ud/search.go index 83f33901ca97938c4d74966f11d3909fdd227061..e8db5749ace91b445f56b7f71ace9c8b5165b886 100644 --- a/ud/search.go +++ b/ud/search.go @@ -28,9 +28,6 @@ type searchCallback func([]contact.Contact, error) // of information is known. func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error { jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout) - if !m.IsRegistered() { - return errors.New("Failed to search: client is not registered.") - } factHashes, factMap := hashFactList(list)