diff --git a/bindings/e2eHandler.go b/bindings/e2eHandler.go index bfd439c133b468c4b0296ec09ae67b7f9045c169..8366299d37676a2d8f43878c66b7eedc8e347cfd 100644 --- a/bindings/e2eHandler.go +++ b/bindings/e2eHandler.go @@ -10,7 +10,8 @@ package bindings import ( "encoding/json" "fmt" - + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/catalog" "gitlab.com/elixxir/client/cmix/identity/receptionID" "gitlab.com/elixxir/client/cmix/rounds" @@ -157,6 +158,42 @@ func (e *E2e) AddService(tag string, processor Processor) error { tag, &messageProcessor{bindingsCbs: processor}) } +// RegisterListener registers a new listener. +// +// Parameters: +// - senderId - the user ID who sends messages to this user that +// this function will register a listener for. +// - messageType - message type from the sender you want to listen for. +// - newListener: A provider for a callback to hear a message. +// Do not pass nil to this. +func (e *E2e) RegisterListener(senderID []byte, + messageType int, + newListener Listener) error { + jww.INFO.Printf("RegisterListener(%v, %d)", senderID, + messageType) + + // Convert senderID to id.Id object + var uid *id.ID + if len(senderID) == 0 { + uid = &id.ID{} + } else { + var err error + uid, err = id.Unmarshal(senderID) + if err != nil { + return errors.New(fmt.Sprintf("Failed to "+ + "ResgisterListener: %+v", err)) + } + } + + // Register listener + // todo: when implementing an unregister function, return and provide a way + // track this listener ID + _ = e.api.GetE2E().RegisterListener(uid, + catalog.MessageType(messageType), listener{l: newListener}) + + return nil +} + // Processor is the bindings-specific interface for message.Processor methods. type Processor interface { Process(message []byte, receptionId []byte, ephemeralId int64, roundId int64) diff --git a/bindings/errors.go b/bindings/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..32c945c80c219626623b39ce12e3f299c1f73cef --- /dev/null +++ b/bindings/errors.go @@ -0,0 +1,121 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 ( + "context" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "strings" + "sync" +) + +// errToUserErr maps backend patterns to user-friendly error messages. +// Example format: +// (Back-end) "Building new HostPool because no HostList stored:": (Front-end) "Missing host list", +var errToUserErr = map[string]string{ + // Registration errors + //"cannot create username when network is not health" : + // "Cannot create username, unable to connect to network", + //"failed to add due to malformed fact stringified facts must at least have a type at the start" : + // "Invalid fact, is the field empty?", + //// UD failures + //"failed to create user discovery manager: cannot return single manager, network is not health" : + // "Could not connect to user discovery", + //"user discovery returned error on search: no results found" : + // "No results found", + //"failed to search.: waiting for response to single-use transmisson timed out after 10s" : + // "Search timed out", + //"the phone number supplied was empty" : "Invalid phone number", + //"failed to create user discovery manager: cannot start ud manager when network follower is not running." : + // "Could not get network status", +} + +// error<Mux is a global lock for the errToUserErr global. +var errorMux sync.RWMutex + +// Error codes +const ( + UnrecognizedCode = "UR: " + UnrecognizedMessage = UnrecognizedCode + "Unrecognized error from XX backend, please report" +) + +// CreateUserFriendlyErrorMessage will convert the passed in error string +// to an error string that is user-friendly if a substring match is +// found to a common error. Common errors is a map which can be updated +// using UpdateCommonErrors. If the error is not common, some simple parsing +// is done on the error message to make it more user-accessible, removing +// backend specific jargon. +// +// Parameters +// - errStr - an error returned from the backend. +// +// Returns +// - A user-friendly error message. This should be devoid of technical speak +// but still be meaningful for front-end or back-end teams. +func CreateUserFriendlyErrorMessage(errStr string) string { + errorMux.RLock() + defer errorMux.RUnlock() + // Go through common errors + for backendErr, userFriendly := range errToUserErr { + // Determine if error contains a common error + if strings.Contains(errStr, backendErr) { + return userFriendly + } + } + + descStr := "desc = " + // If this contains an rpc error, determine how to handle it + if strings.Contains(errStr, context.DeadlineExceeded.Error()) { + // If there is a context deadline exceeded message, return the higher level + // as context deadline exceeded is not informative + rpcErr := "rpc " + rpcIdx := strings.Index(errStr, rpcErr) + return errStr[:rpcIdx] + } else if strings.Contains(errStr, descStr) { + // If containing an rpc error where context deadline exceeded + // is NOT involved, the error returned server-side is often + //more informative + descIdx := strings.Index(errStr, descStr) + // return everything after "desc = " + return errStr[descIdx+len(descStr):] + } + + // If a compound error message, return the highest level message + errParts := strings.Split(errStr, ":") + if len(errParts) > 1 { + // Return everything before the first : + return UnrecognizedCode + errParts[0] + } + + return fmt.Sprintf("%s: %v", UnrecognizedCode, errStr) +} + +// UpdateCommonErrors updates the internal error mapping DB. This internal database +// maps errors returned from the backend to user-friendly error messages. +// +// Parameters +// - jsonFile - contents of a JSON file whose format conforms to the example below. +// Example Input: +// { +// "Failed to Unmarshal Conversation": "Could not retrieve conversation", +// "Failed to unmarshal SentRequestMap": "Failed to pull up friend requests", +// "cannot create username when network is not health": "Cannot create username, unable to connect to network", +// } +func UpdateCommonErrors(jsonFile string) error { + errorMux.Lock() + defer errorMux.Unlock() + err := json.Unmarshal([]byte(jsonFile), &errToUserErr) + if err != nil { + return errors.WithMessage(err, "Failed to unmarshal json file, "+ + "did you pass in the contents or the path?") + } + + return nil +} diff --git a/bindings/errors_test.go b/bindings/errors_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0dbcf0220e1232aa450d0884363e4c461fe42c66 --- /dev/null +++ b/bindings/errors_test.go @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 ( + "context" + "strings" + "testing" +) + +// Unit test +func TestErrorStringToUserFriendlyMessage(t *testing.T) { + // Setup: Populate map + backendErrs := []string{"Failed to Unmarshal Conversation", "failed to create group key preimage", + "Failed to unmarshal SentRequestMap"} + userErrs := []string{"Could not retrieve conversation", "Failed to initiate group chat", + "Failed to pull up friend requests"} + + for i, exampleErr := range backendErrs { + errToUserErr[exampleErr] = userErrs[i] + } + + // Check if a mapped common error returns the expected user-friendly error + received := CreateUserFriendlyErrorMessage(backendErrs[0]) + if strings.Compare(received, userErrs[0]) != 0 { + t.Errorf("Unexpected user friendly message returned from common error mapping."+ + "\n\tExpected: %s"+ + "\n\tReceived: %v", userErrs[0], received) + } + + // Test RPC error in which high level information should + // be passed along (ie context deadline exceeded error) + expected := "Could not poll network: " + rpcPrefix := "rpc error: desc = " + rpcErr := expected + rpcPrefix + context.DeadlineExceeded.Error() + received = CreateUserFriendlyErrorMessage(rpcErr) + if strings.Compare(expected, received) != 0 { + t.Errorf("Rpc error parsed unxecpectedly with error "+ + "\n\"%s\" "+ + "\n\tExpected: %s"+ + "\n\tReceived: %v", rpcErr, UnrecognizedCode+expected, received) + } + + // Test RPC error where server side error information is provided + serverSideError := "Could not parse message! Please try again with a properly crafted message" + rpcErr = rpcPrefix + serverSideError + received = CreateUserFriendlyErrorMessage(rpcErr) + if strings.Compare(serverSideError, received) != 0 { + t.Errorf("RPC error parsed unexpectedly with error "+ + "\n\"%s\" "+ + "\n\tExpected: %s"+ + "\n\tReceived: %v", rpcErr, UnrecognizedCode+serverSideError, received) + } + + // Test uncommon error, should return highest level message + expected = "failed to register with permissioning" + uncommonErr := expected + ": sendRegistrationMessage: Unable to contact Identity Server" + received = CreateUserFriendlyErrorMessage(uncommonErr) + if strings.Compare(received, UnrecognizedCode+expected) != 0 { + t.Errorf("Uncommon error parsed unexpectedly with error "+ + "\n\"%s\" "+ + "\n\tExpected: %s"+ + "\n\tReceived: %s", uncommonErr, UnrecognizedCode+expected, received) + } + + // Test fully unrecognizable and un-parsable message, + // should hardcoded error message + uncommonErr = "failed to register with permissioning" + received = CreateUserFriendlyErrorMessage(uncommonErr) + if strings.Compare(UnrecognizedCode+": "+uncommonErr, received) != 0 { + t.Errorf("Uncommon error parsed unexpectedly with error "+ + "\n\"%s\" "+ + "\n\tExpected: %s"+ + "\n\tReceived: %s", uncommonErr, UnrecognizedMessage, received) + } + +} + +// Unit test +func TestClient_UpdateCommonErrors(t *testing.T) { + + key, expectedVal := "failed to create group key preimage", "Failed to initiate group chat" + + jsonData := "{\"Failed to Unmarshal Conversation\":\"Could not retrieve conversation\",\"Failed to unmarshal SentRequestMap\":\"Failed to pull up friend requests\",\"failed to create group key preimage\":\"Failed to initiate group chat\"}\n" + + err := UpdateCommonErrors(jsonData) + if err != nil { + t.Fatalf("UpdateCommonErrors error: %v", err) + } + + val, ok := errToUserErr[key] + if !ok { + t.Fatalf("Expected entry was not populated") + } + + if strings.Compare(expectedVal, val) != 0 { + t.Fatalf("Entry in updated error map was not expected."+ + "\n\tExpected: %s"+ + "\n\tReceived: %s", expectedVal, val) + } + +} diff --git a/bindings/identity.go b/bindings/identity.go index bda224053da1dc8c557f48cfb6a577be9e00b8cd..4af1ee4275cf5db0e469c9ed87b07a2a6ea39277 100644 --- a/bindings/identity.go +++ b/bindings/identity.go @@ -89,6 +89,12 @@ func (c *Cmix) MakeLegacyReceptionIdentity() ([]byte, error) { return ident.Marshal() } +// GetReceptionRegistrationValidationSignature returns the signature provided by +// the xx network. +func (c *Cmix) GetReceptionRegistrationValidationSignature() []byte { + return c.api.GetStorage().GetReceptionRegistrationValidationSignature() +} + //////////////////////////////////////////////////////////////////////////////// // Contact Functions // //////////////////////////////////////////////////////////////////////////////// diff --git a/bindings/ud.go b/bindings/ud.go index 611d94f49c833f76a6f44f8a43f4f809610470ca..413aabf6d9e029c9472dbf5dd002f5d9b943b364 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -110,6 +110,10 @@ type UdNetworkStatus interface { // Parameters: // - e2eID - e2e object ID in the tracker // - follower - network follower func wrapped in UdNetworkStatus +// - username - the username the user wants to register with UD. +// If the user is already registered, this field may be blank +// - registrationValidationSignature - the signature provided by the xx network. +// This signature is optional for other consumers who deploy their own UD. func LoadOrNewUserDiscovery(e2eID int, follower UdNetworkStatus, username string, registrationValidationSignature []byte) ( *UserDiscovery, error) {