diff --git a/api/client.go b/api/client.go index 6a1e4be6ff55ae9674068302be6a02e1d4d0a3dd..6750a8d220259245e897286ef8bd237cd98cd357 100644 --- a/api/client.go +++ b/api/client.go @@ -29,6 +29,15 @@ import ( "time" ) +type Client struct { + sess user.Session + comm io.Communications + quitReceptionRunner chan bool + listeners *switchboard.Switchboard + // TODO Support more than one wallet per user? Maybe in v2 + wallet *payment.Wallet +} + // Populates a text message and returns its wire representation // TODO support multi-type messages or telling if a message is too long? func FormatTextMessage(message string) []byte { @@ -43,22 +52,27 @@ func FormatTextMessage(message string) []byte { // Initializes the client by registering a storage mechanism. // If none is provided, the system defaults to using OS file access -// returns in error if it fails -func InitClient(s globals.Storage, loc string) error { +// returns a new Client object, and an error if it fails +func InitClient(s globals.Storage, loc string) (*Client, error) { storageErr := globals.InitStorage(s, loc) if storageErr != nil { storageErr = errors.New( "could not init client storage: " + storageErr.Error()) - return storageErr + return nil, storageErr } - return nil + cl := new(Client) + cl.comm = io.NewMessenger() + cl.listeners = switchboard.NewSwitchboard() + // Initialize UDB stuff here + bots.InitUDB(cl.sess, cl.comm, cl.listeners) + return cl, nil } // Registers user and returns the User ID. // Returns an error if registration fails. -func Register(registrationCode string, gwAddr string, +func (cl *Client) Register(registrationCode string, gwAddr string, numNodes uint, mint bool, grp *cyclic.Group) (*id.User, error) { var err error @@ -103,7 +117,7 @@ func Register(registrationCode string, gwAddr string, nus := user.NewSession(u, gwAddr, nk, grp.NewIntFromBytes([]byte("this is not a real public key")), grp) - _, err = payment.CreateWallet(nus, mint) + _, err = payment.CreateWallet(nus, io.NewMessenger(), mint) if err != nil { return id.ZeroID, err } @@ -126,28 +140,26 @@ func Register(registrationCode string, gwAddr string, return UID, err } -var quitReceptionRunner chan bool - -// Logs in user and returns their nickname. -// returns an empty sting if login fails. -func Login(UID *id.User, addr string, tlsCert string) (user.Session, error) { +// Logs in user and sets session on client object +// returns an error if login fails +func (cl *Client) Login(UID *id.User, addr string, tlsCert string) (string, error) { connect.GatewayCertString = tlsCert session, err := user.LoadSession(UID) if session == nil { - return nil, errors.New("Unable to load session: " + err.Error() + + return "", errors.New("Unable to load session: " + err.Error() + fmt.Sprintf("Passed parameters: %q, %s, %q", *UID, addr, tlsCert)) } - theWallet, err = payment.CreateWallet(session, false) + cl.wallet, err = payment.CreateWallet(session, cl.comm,false) if err != nil { err = fmt.Errorf("Login: Couldn't create wallet: %s", err.Error()) globals.Log.ERROR.Printf(err.Error()) - return nil, err + return "", err } - theWallet.RegisterListeners() + cl.wallet.RegisterListeners(cl.listeners) if addr != "" { session.SetGWAddress(addr) @@ -157,65 +169,69 @@ func Login(UID *id.User, addr string, tlsCert string) (user.Session, error) { // TODO: These can be separate, but we set them to the same thing // until registration is completed. - io.SendAddress = addrToUse - io.ReceiveAddress = addrToUse + (cl.comm).(*io.Messaging).SendAddress = addrToUse + (cl.comm).(*io.Messaging).ReceiveAddress = addrToUse if err != nil { err = errors.New(fmt.Sprintf("Login: Could not login: %s", err.Error())) globals.Log.ERROR.Printf(err.Error()) - return nil, err + return "", err } - user.TheSession = session + cl.sess = session pollWaitTimeMillis := 1000 * time.Millisecond - quitReceptionRunner = make(chan bool) + cl.quitReceptionRunner = make(chan bool) // TODO Don't start the message receiver if it's already started. // Should be a pretty rare occurrence except perhaps for mobile. - go io.Messaging.MessageReceiver(pollWaitTimeMillis, quitReceptionRunner) + go cl.comm.MessageReceiver(session, cl.listeners, + pollWaitTimeMillis, cl.quitReceptionRunner) - return session, nil + return session.GetCurrentUser().Nick, nil } // Send prepares and sends a message to the cMix network // FIXME: We need to think through the message interface part. -func Send(message parse.MessageInterface) error { +func (cl *Client) Send(message parse.MessageInterface) error { // FIXME: There should (at least) be a version of this that takes a byte array recipientID := message.GetRecipient() - err := io.Messaging.SendMessage(recipientID, message.Pack()) + err := cl.comm.SendMessage(cl.sess, recipientID, message.Pack()) return err } // DisableBlockingTransmission turns off blocking transmission, for // use with the channel bot and dummy bot -func DisableBlockingTransmission() { - io.BlockTransmissions = false +func (cl *Client) DisableBlockingTransmission() { + (cl.comm).(*io.Messaging).BlockTransmissions = false } // SetRateLimiting sets the minimum amount of time between message // transmissions just for testing, probably to be removed in production -func SetRateLimiting(limit uint32) { - io.TransmitDelay = time.Duration(limit) * time.Millisecond +func (cl *Client) SetRateLimiting(limit uint32) { + (cl.comm).(*io.Messaging).TransmitDelay = time.Duration(limit) * time.Millisecond } -func Listen(user *id.User, outerType format.CryptoType, - messageType int32, newListener switchboard.Listener, callbacks *switchboard. - Switchboard) string { - listenerId := callbacks.Register(user, outerType, messageType, newListener) +func (cl *Client) Listen(user *id.User, outerType format.CryptoType, + messageType int32, newListener switchboard.Listener) string { + listenerId := cl.listeners.Register(user, outerType, messageType, newListener) globals.Log.INFO.Printf("Listening now: user %v, message type %v, id %v", user, messageType, listenerId) return listenerId } -func StopListening(listenerHandle string, callbacks *switchboard.Switchboard) { - callbacks.Unregister(listenerHandle) +func (cl *Client) StopListening(listenerHandle string) { + cl.listeners.Unregister(listenerHandle) +} + +func (cl *Client) GetSwitchboard() *switchboard.Switchboard { + return cl.listeners } type APISender struct{} func (s APISender) Send(messageInterface parse.MessageInterface) { - Send(messageInterface) + //Send(messageInterface) } type Sender interface { @@ -225,23 +241,26 @@ type Sender interface { // Logout closes the connection to the server at this time and does // nothing with the user id. In the future this will release resources // and safely release any sensitive memory. -func Logout() error { - if user.TheSession == nil { +func (cl *Client) Logout() error { + if cl.sess == nil { err := errors.New("Logout: Cannot Logout when you are not logged in") globals.Log.ERROR.Printf(err.Error()) return err } // Stop reception runner goroutine - quitReceptionRunner <- true + cl.quitReceptionRunner <- true // Disconnect from the gateway - io.Disconnect(io.SendAddress) - if io.SendAddress != io.ReceiveAddress { - io.Disconnect(io.ReceiveAddress) + io.Disconnect( + (cl.comm).(*io.Messaging).SendAddress) + if (cl.comm).(*io.Messaging).SendAddress != + (cl.comm).(*io.Messaging).ReceiveAddress { + io.Disconnect( + (cl.comm).(*io.Messaging).ReceiveAddress) } - errStore := user.TheSession.StoreSession() + errStore := cl.sess.StoreSession() // If a client is logging in again, the storage may need to go into a // different location // Currently, none of the storage abstractions need to do anything to @@ -256,7 +275,7 @@ func Logout() error { return err } - errImmolate := user.TheSession.Immolate() + errImmolate := cl.sess.Immolate() if errImmolate != nil { err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" + @@ -265,13 +284,10 @@ func Logout() error { return err } - // Reset listener structure - switchboard.Listeners = switchboard.NewSwitchboard() - return nil } -func RegisterForUserDiscovery(emailAddress string) error { +func (cl *Client) RegisterForUserDiscovery(emailAddress string) error { valueType := "EMAIL" userId, _, err := bots.Search(valueType, emailAddress) if userId != nil { @@ -282,22 +298,22 @@ func RegisterForUserDiscovery(emailAddress string) error { return err } - publicKey := user.TheSession.GetPublicKey() + publicKey := cl.sess.GetPublicKey() publicKeyBytes := publicKey.LeftpadBytes(256) return bots.Register(valueType, emailAddress, publicKeyBytes) } -func SearchForUser(emailAddress string) (*id.User, []byte, error) { +func (cl *Client) SearchForUser(emailAddress string) (*id.User, []byte, error) { valueType := "EMAIL" return bots.Search(valueType, emailAddress) } -func registerUserE2E(partnerID *id.User, +func (cl *Client) registerUserE2E(partnerID *id.User, ownPrivKey *cyclic.Int, partnerPubKey *cyclic.Int) { // Get needed variables from session - grp := user.TheSession.GetGroup() - userID := user.TheSession.GetCurrentUser().User + grp := cl.sess.GetGroup() + userID := cl.sess.GetCurrentUser().User // Generate baseKey baseKey, _ := diffieHellman.CreateDHSessionKey( @@ -319,7 +335,7 @@ func registerUserE2E(partnerID *id.User, km.GenerateKeys(grp, userID) // Add Key Manager to session - user.TheSession.AddKeyManager(km) + cl.sess.AddKeyManager(km) } //Message struct adherent to interface in bindings for data return from ParseMessage @@ -361,30 +377,27 @@ func ParseMessage(message []byte)(ParsedMessage,error){ return pm, nil } -// TODO Support more than one wallet per user? Maybe in v2 -var theWallet *payment.Wallet - -func Wallet() *payment.Wallet { - if theWallet == nil { +func (cl *Client) Wallet() *payment.Wallet { + if cl.wallet == nil { // Assume that the correct wallet is already stored in the session // (if necessary, minted during register) // So, if the wallet is nil, registration must have happened for this method to work var err error - theWallet, err = payment.CreateWallet(user.TheSession, false) - theWallet.RegisterListeners() + cl.wallet, err = payment.CreateWallet(cl.sess, cl.comm, false) + cl.wallet.RegisterListeners(cl.listeners) if err != nil { globals.Log.ERROR.Println("Wallet("+ "): Got an error creating the wallet.", err.Error()) } } - return theWallet + return cl.wallet +} + +func (cl *Client) GetSessionData() ([]byte, error) { + return cl.sess.GetSessionData() } // Set the output of the func SetLogOutput(w goio.Writer) { globals.Log.SetLogOutput(w) } - -func GetSessionData() ([]byte, error) { - return user.TheSession.GetSessionData() -} diff --git a/bindings/client.go b/bindings/client.go index 5c0b3c8a69726326d57b2dd72376d2c1c7e00855..7a253223e076642db06111d2acb9a23055d30f85 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -11,7 +11,6 @@ import ( "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" - "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/crypto/certs" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/primitives/format" @@ -20,6 +19,10 @@ import ( "io" ) +type Client struct { + client *api.Client +} + // Copy of the storage interface. // It is identical to the interface used in Globals, // and a results the types can be passed freely between the two @@ -65,20 +68,19 @@ type Listener interface { // messages sent from all users. // If you pass the zero type (just zero) to Listen() you will hear messages of // all types. -func Listen(userId []byte, messageType int32, newListener Listener) string { +func (cl *Client) Listen(userId []byte, messageType int32, newListener Listener) string { typedUserId := new(id.User).SetBytes(userId) listener := &listenerProxy{proxy: newListener} - return api.Listen(typedUserId, format.None, messageType, - listener, switchboard.Listeners) + return cl.client.Listen(typedUserId, format.None, messageType, listener) } // Returns a parsed message // Pass the listener handle that Listen() returned to delete the listener -func StopListening(listenerHandle string) { - api.StopListening(listenerHandle, switchboard.Listeners) +func (cl *Client) StopListening(listenerHandle string) { + cl.client.StopListening(listenerHandle) } func FormatTextMessage(message string) []byte { @@ -98,15 +100,15 @@ func FormatTextMessage(message string) []byte { // loc is a string. If you're using DefaultStorage for your storage, // this would be the filename of the file that you're storing the user // session in. -func InitClient(storage Storage, loc string) error { +func InitClient(storage Storage, loc string) (*Client, error) { if storage == nil { - return errors.New("could not init client: Storage was nil") + return nil, errors.New("could not init client: Storage was nil") } proxy := &storageProxy{boundStorage: storage} - err := api.InitClient(globals.Storage(proxy), loc) + cl, err := api.InitClient(globals.Storage(proxy), loc) - return err + return &Client{client: cl}, err } // Registers user and returns the User ID. Returns null if registration fails. @@ -145,7 +147,7 @@ func InitClient(storage Storage, loc string) error { // 10 // “Jono” // OIF3OJ5I -func Register(registrationCode string, gwAddr string, numNodes int, +func (cl *Client) Register(registrationCode string, gwAddr string, numNodes int, mint bool, grpJSON string) ([]byte, error) { if numNodes < 1 { @@ -159,7 +161,7 @@ func Register(registrationCode string, gwAddr string, numNodes int, return id.ZeroID[:], err } - UID, err := api.Register(registrationCode, gwAddr, uint(numNodes), mint, &grp) + UID, err := cl.client.Register(registrationCode, gwAddr, uint(numNodes), mint, &grp) if err != nil { return id.ZeroID[:], err @@ -176,30 +178,26 @@ func Register(registrationCode string, gwAddr string, numNodes int, // certificate and it's in the crypto repository. So, if you leave the TLS // certificate string empty, the bindings will use that certificate. We probably // need to rethink this. In other words, tlsCert is optional. -func Login(UID []byte, addr string, tlsCert string) (string, error) { +func (cl *Client) Login(UID []byte, addr string, tlsCert string) (string, error) { userID := new(id.User).SetBytes(UID) var err error - var session user.Session + var nick string if tlsCert == "" { - session, err = api.Login(userID, addr, certs.GatewayTLS) - } else { - session, err = api.Login(userID, addr, tlsCert) - } - if err != nil || session == nil { - return "", err + nick, err = cl.client.Login(userID, addr, certs.GatewayTLS) } else { - return session.GetCurrentUser().Nick, err + nick, err = cl.client.Login(userID, addr, tlsCert) } + return nick, err } //Sends a message structured via the message interface // Automatically serializes the message type before the rest of the payload // Returns an error if either sender or recipient are too short -func Send(m Message) error { +func (cl *Client) Send(m Message) error { sender := new(id.User).SetBytes(m.GetSender()) recipient := new(id.User).SetBytes(m.GetRecipient()) - return api.Send(&parse.Message{ + return cl.client.Send(&parse.Message{ TypedBody: parse.TypedBody{ MessageType: m.GetMessageType(), Body: m.GetPayload(), @@ -212,24 +210,24 @@ func Send(m Message) error { // Logs the user out, saving the state for the system and clearing all data // from RAM -func Logout() error { - return api.Logout() +func (cl *Client) Logout() error { + return cl.client.Logout() } // Turns off blocking transmission so multiple messages can be sent // simultaneously -func DisableBlockingTransmission() { - api.DisableBlockingTransmission() +func (cl *Client) DisableBlockingTransmission() { + cl.client.DisableBlockingTransmission() } // Sets the minimum amount of time, in ms, between message transmissions // Just for testing, probably to be removed in production -func SetRateLimiting(limit int) { - api.SetRateLimiting(uint32(limit)) +func (cl *Client) SetRateLimiting(limit int) { + cl.client.SetRateLimiting(uint32(limit)) } -func RegisterForUserDiscovery(emailAddress string) error { - return api.RegisterForUserDiscovery(emailAddress) +func (cl *Client) RegisterForUserDiscovery(emailAddress string) error { + return cl.client.RegisterForUserDiscovery(emailAddress) } type SearchResult struct { @@ -237,8 +235,8 @@ type SearchResult struct { PublicKey []byte } -func SearchForUser(emailAddress string) (*SearchResult, error) { - searchedUser, key, err := api.SearchForUser(emailAddress) +func (cl *Client) SearchForUser(emailAddress string) (*SearchResult, error) { + searchedUser, key, err := cl.client.SearchForUser(emailAddress) if err != nil { return nil, err } else { @@ -292,6 +290,6 @@ func SetLogOutput(w Writer) { } // Call this to get the session data without getting Save called from the Go side -func GetSessionData() ([]byte, error) { - return api.GetSessionData() +func (cl *Client) GetSessionData() ([]byte, error) { + return cl.client.GetSessionData() } diff --git a/bindings/payment.go b/bindings/payment.go index a2db5b0add29476538affb66f250b2ea9b0dbfb5..4b243cea13bc5b229e0f90d40e0c39236f0b8f84 100644 --- a/bindings/payment.go +++ b/bindings/payment.go @@ -9,18 +9,17 @@ package bindings import ( "errors" "fmt" - "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/cmixproto" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/payment" - "gitlab.com/elixxir/primitives/id" "gitlab.com/elixxir/primitives/format" + "gitlab.com/elixxir/primitives/id" ) // Currently there's only one wallet that you can get // There may be many in the future -func GetActiveWallet() *Wallet { - return &Wallet{wallet: api.Wallet()} +func (cl *Client) GetActiveWallet() *Wallet { + return &Wallet{wallet: cl.client.Wallet()} } func (w *Wallet) Listen(userId []byte, outerType format.CryptoType, @@ -29,17 +28,17 @@ func (w *Wallet) Listen(userId []byte, outerType format.CryptoType, listener := &listenerProxy{proxy: newListener} - return api.Listen(typedUserId, outerType, innerType, - listener, w.wallet.GetSwitchboard()) + return w.wallet.GetSwitchboard(). + Register(typedUserId, outerType, innerType, listener) } func (w *Wallet) StopListening(listenerHandle string) { - api.StopListening(listenerHandle, w.wallet.GetSwitchboard()) + w.wallet.GetSwitchboard().Unregister(listenerHandle) } // Returns the currently available balance in the wallet func (w *Wallet) GetAvailableFunds() int64 { - return int64(api.Wallet().GetAvailableFunds()) + return int64(w.wallet.GetAvailableFunds()) } // Payer: user ID, 256 bits diff --git a/bots/userDiscovery.go b/bots/userDiscovery.go index ce4013d01e9ba758de19c7324f60ed01abe14767..4b7251aa763b870281300eee370b428c42f003a1 100644 --- a/bots/userDiscovery.go +++ b/bots/userDiscovery.go @@ -14,6 +14,7 @@ import ( "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/io" "gitlab.com/elixxir/client/parse" + "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/primitives/switchboard" "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/primitives/id" @@ -21,11 +22,14 @@ import ( "gitlab.com/elixxir/primitives/format" ) -// UdbID is the ID of the user discovery bot, which is always 13 +// UdbID is the ID of the user discovery bot, which is always 3 var UdbID *id.User type udbResponseListener chan string +var session user.Session +var messaging io.Communications + var pushKeyResponseListener udbResponseListener var getKeyResponseListener udbResponseListener var registerResponseListener udbResponseListener @@ -36,8 +40,8 @@ func (l *udbResponseListener) Hear(msg switchboard.Item, isHeardElsewhere bool) *l <- string(m.Body) } -// The go runtime calls init() before calling any methods in the package -func init() { +// InitUDB is called internally by the InitClient API +func InitUDB(s user.Session,m io.Communications, l *switchboard.Switchboard) { UdbID = new(id.User).SetUints(&[4]uint64{0, 0, 0, 3}) pushKeyResponseListener = make(udbResponseListener) @@ -45,16 +49,19 @@ func init() { registerResponseListener = make(udbResponseListener) searchResponseListener = make(udbResponseListener) - switchboard.Listeners.Register(UdbID, + session = s + messaging = m + + l.Register(UdbID, format.None, int32(cmixproto.Type_UDB_PUSH_KEY_RESPONSE), &pushKeyResponseListener) - switchboard.Listeners.Register(UdbID, + l.Register(UdbID, format.None, int32(cmixproto.Type_UDB_GET_KEY_RESPONSE), &getKeyResponseListener) - switchboard.Listeners.Register(UdbID, + l.Register(UdbID, format.None, int32(cmixproto.Type_UDB_REGISTER_RESPONSE), ®isterResponseListener) - switchboard.Listeners.Register(UdbID, + l.Register(UdbID, format.None, int32(cmixproto.Type_UDB_SEARCH_RESPONSE), &searchResponseListener) } @@ -224,5 +231,5 @@ func fingerprint(publicKey []byte) string { // Callers that need to wait on a response should implement waiting with a // listener. func sendCommand(botID *id.User, command []byte) error { - return io.Messaging.SendMessage(botID, command) + return messaging.SendMessage(session, botID, command) } diff --git a/cmd/root.go b/cmd/root.go index 09f44335bb81becb9ccf36a67516a82aa1822fb1..3e237639db0fb99977ff3306993a766ec1d44f4f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,6 +22,7 @@ import ( "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/comms/connect" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/id" "gitlab.com/elixxir/primitives/switchboard" @@ -46,6 +47,7 @@ var mint bool var rateLimiting uint32 var showVer bool var certPath string +var client *api.Client // Execute adds all child commands to the root command and sets flags // appropriately. This is called by main.main(). It only needs to @@ -58,18 +60,12 @@ func Execute() { } func sessionInitialization() { - if noBlockingTransmission { - api.DisableBlockingTransmission() - } - - bindings.SetRateLimiting(int(rateLimiting)) - var err error register := false //If no session file is passed initialize with RAM Storage if sessionFile == "" { - err = bindings.InitClient(&globals.RamStorage{}, "") + client, err = api.InitClient(&globals.RamStorage{}, "") if err != nil { fmt.Printf("Could Not Initialize Ram Storage: %s\n", err.Error()) @@ -92,7 +88,7 @@ func sessionInitialization() { } //Initialize client with OS Storage - err = bindings.InitClient(&globals.DefaultStorage{}, sessionFile) + client, err = api.InitClient(&globals.DefaultStorage{}, sessionFile) if err != nil { fmt.Printf("Could Not Initialize OS Storage: %s\n", err.Error()) @@ -100,6 +96,12 @@ func sessionInitialization() { } } + if noBlockingTransmission { + client.DisableBlockingTransmission() + } + + client.SetRateLimiting(rateLimiting) + // Handle parsing gateway addresses from the config file gateways := viper.GetStringSlice("gateways") if gwAddr == "" { @@ -123,8 +125,15 @@ func sessionInitialization() { // 64 bits grpJSON := viper.GetString("group") + // Unmarshal group JSON + var grp cyclic.Group + err := grp.UnmarshalJSON([]byte(grpJSON)) + if err != nil { + return + } + regCode := new(id.User).SetUints(&[4]uint64{0, 0, 0, userId}).RegistrationCode() - _, err = bindings.Register(regCode, gwAddr, int(numNodes), mint, grpJSON) + _, err = client.Register(regCode, gwAddr, numNodes, mint, &grp) if err != nil { fmt.Printf("Could Not Register User: %s\n", err.Error()) return @@ -133,7 +142,7 @@ func sessionInitialization() { // Log the user in uid := id.NewUserFromUint(userId, nil) - _, err = bindings.Login(uid[:], gwAddr, "") + _, err = client.Login(uid, gwAddr, "") if err != nil { fmt.Printf("Could Not Log In\n") @@ -211,7 +220,7 @@ func (l *ChannelListener) Hear(item switchboard.Item, isHeardElsewhere bool) { new(big.Int).SetBytes(message.Sender[:]).Text(10), senderNick) typedBody, _ := parse.Parse(result.Message) speakerId := new(id.User).SetBytes(result.SpeakerID) - switchboard.Listeners.Speak(&parse.Message{ + client.GetSwitchboard().Speak(&parse.Message{ TypedBody: *typedBody, Sender: speakerId, Receiver: id.ZeroID, @@ -241,21 +250,21 @@ var rootCmd = &cobra.Command{ // Set the GatewayCertPath explicitly to avoid data races SetCertPath(certPath) + sessionInitialization() // Set up the listeners for both of the types the client needs for // the integration test // Normal text messages text := TextListener{} - api.Listen(id.ZeroID, format.None, int32(cmixproto.Type_TEXT_MESSAGE), - &text, switchboard.Listeners) + client.Listen(id.ZeroID, format.None, int32(cmixproto.Type_TEXT_MESSAGE), + &text) // Channel messages channel := ChannelListener{} - api.Listen(id.ZeroID, format.None, - int32(cmixproto.Type_CHANNEL_MESSAGE), &channel, - switchboard.Listeners) + client.Listen(id.ZeroID, format.None, + int32(cmixproto.Type_CHANNEL_MESSAGE), &channel) // All other messages fallback := FallbackListener{} - api.Listen(id.ZeroID, format.None, int32(cmixproto.Type_NO_TYPE), - &fallback, switchboard.Listeners) + client.Listen(id.ZeroID, format.None, int32(cmixproto.Type_NO_TYPE), + &fallback) // Do calculation for dummy messages if the flag is set if dummyFrequency != 0 { @@ -263,8 +272,6 @@ var rootCmd = &cobra.Command{ (time.Duration(float64(1000000000) * (float64(1.0) / dummyFrequency))) } - sessionInitialization() - // Only send a message if we have a message to send (except dummy messages) recipientId := new(id.User).SetUints(&[4]uint64{0, 0, 0, destinationUserId}) senderId := new(id.User).SetUints(&[4]uint64{0, 0, 0, userId}) @@ -278,8 +285,7 @@ var rootCmd = &cobra.Command{ // Handle sending to UDB if *recipientId == *bots.UdbID { - grp := user.TheSession.GetGroup() - fmt.Println(parseUdbMessage(message, grp)) + fmt.Println(parseUdbMessage(message, client)) } else { // Handle sending to any other destination wireOut := bindings.FormatTextMessage(message) @@ -288,7 +294,7 @@ var rootCmd = &cobra.Command{ recipientNick, message) // Send the message - bindings.Send(&parse.BindingsMessageProxy{&parse.Message{ + client.Send(&parse.Message{ Sender: senderId, TypedBody: parse.TypedBody{ MessageType: int32(cmixproto.Type_TEXT_MESSAGE), @@ -296,7 +302,7 @@ var rootCmd = &cobra.Command{ }, CryptoType: format.Unencrypted, Receiver: recipientId, - }}) + }) } } @@ -317,15 +323,15 @@ var rootCmd = &cobra.Command{ fmt.Printf("Sending Message to %d, %v: %s\n", destinationUserId, contact, message) - message := &parse.BindingsMessageProxy{&parse.Message{ + message := &parse.Message{ Sender: senderId, TypedBody: parse.TypedBody{ MessageType: int32(cmixproto.Type_TEXT_MESSAGE), Body: bindings.FormatTextMessage(message), }, CryptoType: format.Unencrypted, - Receiver: recipientId}} - bindings.Send(message) + Receiver: recipientId} + client.Send(message) timer = time.NewTimer(dummyPeriod) } @@ -343,7 +349,7 @@ var rootCmd = &cobra.Command{ } //Logout - err := bindings.Logout() + err := client.Logout() if err != nil { fmt.Printf("Could not logout: %s\n", err.Error()) diff --git a/cmd/udb.go b/cmd/udb.go index bfe0596a196f6f477c73c674d2f0a9215fa82f2b..2d638c6b23a369187809898521fd04a9534e2a36 100644 --- a/cmd/udb.go +++ b/cmd/udb.go @@ -9,13 +9,12 @@ package cmd import ( "fmt" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/bindings" - "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/client/api" "strings" ) // Determines what UDB send function to call based on the text in the message -func parseUdbMessage(msg string, grp *cyclic.Group) string { +func parseUdbMessage(msg string, client *api.Client) string { // Split the message on spaces args := strings.Fields(msg) if len(args) < 3 { @@ -27,16 +26,15 @@ func parseUdbMessage(msg string, grp *cyclic.Group) string { keyword := args[0] // Case-insensitive match the keyword to a command if strings.EqualFold(keyword, "SEARCH") { - result, err := bindings.SearchForUser(args[2]) + userID, pubKey, err := client.SearchForUser(args[2]) if err != nil { return fmt.Sprintf("UDB search failed: %v", err.Error()) } else { - userIdText := grp.NewIntFromBytes(result.ResultID).Text(10) return fmt.Sprintf("UDB search successful. Returned user %v, "+ - "public key %q", userIdText, result.PublicKey) + "public key %q", *userID, pubKey) } } else if strings.EqualFold(keyword, "REGISTER") { - err := bindings.RegisterForUserDiscovery(args[2]) + err := client.RegisterForUserDiscovery(args[2]) if err != nil { return fmt.Sprintf("UDB registration failed: %v", err.Error()) } else { diff --git a/io/collate.go b/io/collate.go index 934519e10d7ad2a11b0eea68c19afecc3a234a9a..158c26961426b1a134f4b9d4428bbc55459ba5ac 100644 --- a/io/collate.go +++ b/io/collate.go @@ -11,7 +11,6 @@ import ( "fmt" "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" - "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/primitives/format" "sync" "time" @@ -75,7 +74,7 @@ func (mb *collator) AddMessage(message *format.Message, TypedBody: *typedBody, CryptoType: format.Unencrypted, Sender: sender, - Receiver: user.TheSession.GetCurrentUser().User, + Receiver: recipient, } return &msg diff --git a/io/interface.go b/io/interface.go index fe8b169a79a59de285d4174830cf5252817333b0..b04aecf3d1855c38ec6c4ed52831262167ce3653 100644 --- a/io/interface.go +++ b/io/interface.go @@ -8,14 +8,17 @@ package io import ( + "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/primitives/id" + "gitlab.com/elixxir/primitives/switchboard" "time" ) // Communication interface implements send/receive functionality with the server type Communications interface { // SendMessage to the server - SendMessage(recipientID *id.User, message []byte) error + SendMessage(session user.Session, recipientID *id.User, message []byte) error // MessageReceiver thread to get new messages - MessageReceiver(delay time.Duration, quit chan bool) + MessageReceiver(session user.Session, sw *switchboard.Switchboard, + delay time.Duration, quit chan bool) } diff --git a/io/messaging.go b/io/messaging.go index 8ced2ef77c002013a2f5edc96e3867b5a45c1717..aaf786b1ddcfc5d165be7aea647eb4913c3b2976 100644 --- a/io/messaging.go +++ b/io/messaging.go @@ -28,38 +28,40 @@ import ( "time" ) -type messaging struct { +// Messaging implements the Communications interface +type Messaging struct { nextId func() []byte + // SendAddress is the address of the server to send messages + SendAddress string + // ReceiveAddress is the address of the server to receive messages from + ReceiveAddress string + // BlockTransmissions will use a mutex to prevent multiple threads from sending + // messages at the same time. + BlockTransmissions bool + // TransmitDelay is the minimum delay between transmissions. + TransmitDelay time.Duration + // Map that holds a record of the messages that this client successfully + // received during this session + ReceivedMessages map[string]struct{} + sendLock sync.Mutex } -// Messaging implements the Communications interface -var Messaging Communications = &messaging{nextId: parse.IDCounter()} - -// SendAddress is the address of the server to send messages -var SendAddress string - -// ReceiveAddress is the address of the server to receive messages from -var ReceiveAddress string - -// BlockTransmissions will use a mutex to prevent multiple threads from sending -// messages at the same time. -var BlockTransmissions = true - -// TransmitDelay is the minimum delay between transmissions. -var TransmitDelay = 1000 * time.Millisecond - -// Map that holds a record of the messages that this client successfully -// received during this session -var ReceivedMessages map[string]struct{} - -var sendLock sync.Mutex +func NewMessenger() *Messaging { + return &Messaging{ + nextId: parse.IDCounter(), + BlockTransmissions: true, + TransmitDelay: 1000 * time.Millisecond, + ReceivedMessages: make(map[string]struct{}), + } +} // SendMessage to the provided Recipient // TODO: It's not clear why we wouldn't hand off a sender object (with // the keys) here. I won't touch crypto at this time, though... // TODO This method would be cleaner if it took a parse.Message (particularly // w.r.t. generating message IDs for multi-part messages.) -func (m *messaging) SendMessage(recipientID *id.User, +func (m *Messaging) SendMessage(session user.Session, + recipientID *id.User, message []byte) error { // FIXME: We should really bring the plaintext parts of the NewMessage logic // into this module, then have an EncryptedMessage type that is sent to/from @@ -70,8 +72,8 @@ func (m *messaging) SendMessage(recipientID *id.User, // TBD: Is there a really good reason why we'd ever have more than one user // in this library? why not pass a sender object instead? globals.Log.DEBUG.Printf("Sending message to %q: %q", *recipientID, message) - userID := user.TheSession.GetCurrentUser().User - grp := user.TheSession.GetGroup() + userID := session.GetCurrentUser().User + grp := session.GetGroup() parts, err := parse.Partition([]byte(message), m.nextId()) if err != nil { @@ -101,7 +103,7 @@ func (m *messaging) SendMessage(recipientID *id.User, // The timestamp will be encrypted later message.SetTimestamp(nowBytes) message.SetPayloadData(parts[i]) - err = send(userID, message, grp) + err = m.send(session, userID, message, grp) if err != nil { return fmt.Errorf("SendMessage send() error: %v", err.Error()) } @@ -110,13 +112,14 @@ func (m *messaging) SendMessage(recipientID *id.User, } // send actually sends the message to the server -func send(senderID *id.User, message *format.Message, grp *cyclic.Group) error { +func (m *Messaging) send(session user.Session, + senderID *id.User, message *format.Message, grp *cyclic.Group) error { // Enable transmission blocking if enabled - if BlockTransmissions { - sendLock.Lock() + if m.BlockTransmissions { + m.sendLock.Lock() defer func() { - time.Sleep(TransmitDelay) - sendLock.Unlock() + time.Sleep(m.TransmitDelay) + m.sendLock.Unlock() }() } @@ -127,7 +130,7 @@ func send(senderID *id.User, message *format.Message, grp *cyclic.Group) error { // Generate a compound encryption key encryptionKey := grp.NewInt(1) - for _, key := range user.TheSession.GetKeys() { + for _, key := range session.GetKeys() { baseKey := key.TransmissionKeys.Base partialEncryptionKey := cmix.NewEncryptionKey(salt, baseKey, grp) grp.Mul(encryptionKey, partialEncryptionKey, encryptionKey) @@ -150,7 +153,7 @@ func send(senderID *id.User, message *format.Message, grp *cyclic.Group) error { var err error globals.Log.INFO.Println("Sending put message to gateway") - err = client.SendPutMessage(SendAddress, msgPacket) + err = client.SendPutMessage(m.SendAddress, msgPacket) return err } @@ -160,13 +163,15 @@ func send(senderID *id.User, message *format.Message, grp *cyclic.Group) error { // list for the listeners? // Accessing all of these global variables is extremely problematic for this // kind of thread. -func (m *messaging) MessageReceiver(delay time.Duration, quit chan bool) { +func (m *Messaging) MessageReceiver(session user.Session, + sw *switchboard.Switchboard, + delay time.Duration, quit chan bool) { // FIXME: It's not clear we should be doing decryption here. - if user.TheSession == nil { + if session == nil { globals.Log.FATAL.Panicf("No user session available") } pollingMessage := pb.ClientPollMessage{ - UserID: user.TheSession.GetCurrentUser().User.Bytes(), + UserID: session.GetCurrentUser().User.Bytes(), } for { @@ -177,14 +182,14 @@ func (m *messaging) MessageReceiver(delay time.Duration, quit chan bool) { default: time.Sleep(delay) globals.Log.INFO.Printf("Attempting to receive message from gateway") - decryptedMessages := m.receiveMessagesFromGateway(&pollingMessage) + decryptedMessages := m.receiveMessagesFromGateway(session, &pollingMessage) if decryptedMessages != nil { for i := range decryptedMessages { assembledMessage := GetCollator().AddMessage( decryptedMessages[i], time.Minute) if assembledMessage != nil { // we got a fully assembled message. let's broadcast it - broadcastMessageReception(assembledMessage, switchboard.Listeners) + broadcastMessageReception(assembledMessage, sw) } } } @@ -192,11 +197,11 @@ func (m *messaging) MessageReceiver(delay time.Duration, quit chan bool) { } } -func (m *messaging) receiveMessagesFromGateway( +func (m *Messaging) receiveMessagesFromGateway(session user.Session, pollingMessage *pb.ClientPollMessage) []*format.Message { - if user.TheSession != nil { - pollingMessage.MessageID = user.TheSession.GetLastMessageID() - messages, err := client.SendCheckMessages(user.TheSession.GetGWAddress(), + if session != nil { + pollingMessage.MessageID = session.GetLastMessageID() + messages, err := client.SendCheckMessages(session.GetGWAddress(), pollingMessage) if err != nil { @@ -206,24 +211,20 @@ func (m *messaging) receiveMessagesFromGateway( globals.Log.INFO.Printf("Checking novelty of %v messages", len(messages.MessageIDs)) - if ReceivedMessages == nil { - ReceivedMessages = make(map[string]struct{}) - } - results := make([]*format.Message, 0, len(messages.MessageIDs)) - grp := user.TheSession.GetGroup() + grp := session.GetGroup() for _, messageID := range messages.MessageIDs { // Get the first unseen message from the list of IDs - _, received := ReceivedMessages[messageID] + _, received := m.ReceivedMessages[messageID] if !received { globals.Log.INFO.Printf("Got a message waiting on the gateway: %v", messageID) // We haven't seen this message before. // So, we should retrieve it from the gateway. - newMessage, err := client.SendGetMessage(user. - TheSession.GetGWAddress(), + newMessage, err := client.SendGetMessage( + session.GetGWAddress(), &pb.ClientPollMessage{ - UserID: user.TheSession.GetCurrentUser().User. + UserID: session.GetCurrentUser().User. Bytes(), MessageID: messageID, }) @@ -241,7 +242,7 @@ func (m *messaging) receiveMessagesFromGateway( // Generate a compound decryption key salt := newMessage.Salt decryptionKey := grp.NewInt(1) - for _, key := range user.TheSession.GetKeys() { + for _, key := range session.GetKeys() { baseKey := key.ReceptionKeys.Base partialDecryptionKey := cmix.NewDecryptionKey(salt, baseKey, grp) @@ -251,9 +252,9 @@ func (m *messaging) receiveMessagesFromGateway( globals.Log.INFO.Printf( "Adding message ID %v to received message IDs", messageID) - ReceivedMessages[messageID] = struct{}{} - user.TheSession.SetLastMessageID(messageID) - user.TheSession.StoreSession() + m.ReceivedMessages[messageID] = struct{}{} + session.SetLastMessageID(messageID) + session.StoreSession() decryptedMsg, err2 := crypto.Decrypt(decryptionKey, grp, newMessage) diff --git a/payment/wallet.go b/payment/wallet.go index eedd7762b369a25199a7da390df33f64c66d0adf..e3e485f21e97806bb6b9aea600f3a0c1f2844c40 100644 --- a/payment/wallet.go +++ b/payment/wallet.go @@ -44,6 +44,7 @@ type Wallet struct { completedOutboundPayments *TransactionList session user.Session + comm io.Communications // Listen to this switchboard to get UI messages from the wallet. // This includes the types PAYMENT_INVOICE_UI, PAYMENT_RESPONSE, and @@ -81,7 +82,9 @@ func (w *Wallet) GetSwitchboard() *switchboard.Switchboard { // If you want the wallet to be able to receive messages you must register its // listeners // api.Login does this, while api.Register (during minting) does not -func CreateWallet(s user.Session, doMint bool) (*Wallet, error) { +func CreateWallet(s user.Session, + comm io.Communications, + doMint bool) (*Wallet, error) { cs, err := CreateOrderedStorage(CoinStorageTag, s) @@ -136,6 +139,7 @@ func CreateWallet(s user.Session, doMint bool) (*Wallet, error) { completedInboundPayments: ip, completedOutboundPayments: op, session: s, + comm: comm, switchboard: sb, } @@ -145,18 +149,19 @@ func CreateWallet(s user.Session, doMint bool) (*Wallet, error) { // You need to call this method after creating the wallet to have the wallet // behave correctly when receiving messages // TODO: Should this take the listeners as parameters? -func (w *Wallet) RegisterListeners() { - switchboard.Listeners.Register(id.ZeroID, +func (w *Wallet) RegisterListeners( + listeners *switchboard.Switchboard) { + listeners.Register(id.ZeroID, format.None, int32(cmixproto.Type_PAYMENT_INVOICE), &InvoiceListener{ wallet: w, }) - switchboard.Listeners.Register(getPaymentBotID(), + listeners.Register(getPaymentBotID(), format.None, int32(cmixproto.Type_PAYMENT_RESPONSE), &ResponseListener{ wallet: w, }) - switchboard.Listeners.Register(id.ZeroID, + listeners.Register(id.ZeroID, format.None, int32(cmixproto.Type_PAYMENT_RECEIPT), &ReceiptListener{ wallet: w, @@ -428,7 +433,8 @@ func (l *ResponseListener) Hear(msg switchboard.Item, isHeardElsewhere bool) { receipt := l.formatReceipt(transaction) globals.Log.DEBUG.Printf("Attempting to send receipt to transaction"+ " recipient: %v!", transaction.Recipient) - err := io.Messaging.SendMessage(transaction.Recipient, + err := l.wallet.comm.SendMessage(l.wallet.session, + transaction.Recipient, receipt.Pack()) if err != nil { globals.Log.ERROR.Printf("Payment response listener couldn't send"+ diff --git a/payment/wallet_test.go b/payment/wallet_test.go index 36b1c784c3df93028bc5fffbdf9ff25d107ae2c9..7a3b1508186e535f1fb8217de5a26c61af9eeb51 100644 --- a/payment/wallet_test.go +++ b/payment/wallet_test.go @@ -94,7 +94,7 @@ func TestCreateWallet(t *testing.T) { s := user.NewSession(&user.User{User: id.NewUserFromUint(1, t), Nick: "test"}, "", []user.NodeKeys{}, grp.NewInt(1), grp) - _, err := CreateWallet(s, false) + _, err := CreateWallet(s, io.NewMessenger(), false) if err != nil { t.Errorf("CreateWallet: error returned on valid wallet creation: %s", err.Error()) diff --git a/user/session.go b/user/session.go index 5611d5eb6cfa6bf0985e5cab4524ad17948df9f2..a20372da723b6bd7fff4dc82a6ae05ad6a9566d7 100644 --- a/user/session.go +++ b/user/session.go @@ -24,10 +24,6 @@ import ( // Errors var ErrQuery = errors.New("element not in map") -// Globally instantiated Session -// FIXME remove this sick filth -var TheSession Session - // Interface for User Session operations type Session interface { GetCurrentUser() (currentUser *User) @@ -132,8 +128,6 @@ func LoadSession(UID *id.User) (Session, error) { km.GenerateKeys(session.Grp, UID) } - TheSession = &session - return &session, nil } @@ -283,8 +277,6 @@ func (s *SessionObj) Immolate() error { clearRatchetKeys(&s.Keys[i].ReceptionKeys) } - TheSession = nil - s.UnlockStorage() return nil