From b5c88b43b227efedc6fea730f029d96065cdebfc Mon Sep 17 00:00:00 2001 From: joshemb <josh@elixxir.io> Date: Tue, 30 Aug 2022 13:24:40 -0700 Subject: [PATCH] WIP: Implement Notifications for bindings --- bindings/notifications.go | 153 +++++++++++++++++++++++---------- bindings/notifications_test.go | 28 ++++++ cmix/interface.go | 3 + cmix/message/handler.go | 1 + cmix/message/serviceTracker.go | 28 ++++-- 5 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 bindings/notifications_test.go diff --git a/bindings/notifications.go b/bindings/notifications.go index bbc79ca79..6d36a96ef 100644 --- a/bindings/notifications.go +++ b/bindings/notifications.go @@ -7,67 +7,130 @@ package bindings -// FIXME: This is the old NotificationsForMe code that needs to be fixed -/* -type NotificationForMeReport struct { - ForMe bool - Type string - Source []byte -} +import ( + "encoding/json" + "gitlab.com/elixxir/primitives/notifications" +) + +// NotificationReports is a list of NotificationReport's. This will be returned +// via NotificationsForMe as a JSON marshalled byte data. +// +// Example JSON: +// +// [ +// { +// "ForMe": true, +// "Type": "e2e", +// "Source": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD" +// }, +// { +// "ForMe": true, +// "Type": "e2e", +// "Source": "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD" +// }, +// { +// "ForMe": true, +// "Type": "e2e", +// "Source": "AAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD" +// } +//] +type NotificationReports []NotificationReport -type ManyNotificationForMeReport struct { - Many []*NotificationForMeReport +// NotificationReport is the bindings' representation for notifications for +// this user. +// +// Example NotificationReport JSON: +// +// { +// "ForMe": true, +// "Type": "e2e", +// "Source": "dGVzdGVyMTIzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +//} +// +// Given the Type, the Source value will have specific contextual meanings. +// Below is a table that will define the contextual meaning of the Source field +// given all possible Type fields. +// +// TYPE SOURCE DESCRIPTION +// "default" recipient user ID A message with no association. +// "request" sender user ID A channel request has been received, from Source. +// "reset" sender user ID A channel reset has been received. +// "confirm" sender user ID A channel request has been accepted. +// "silent" sender user ID A message where the user should not be notified. +// "e2e" sender user ID A reception of an E2E message. +// "group" group ID A reception of a group chat message. +// "endFT" sender user ID The last message sent confirming end of file transfer. +// "groupRQ" sender user ID A request from Source to join a group chat. +// todo iterate over this docstring, ensure descriptions/sources are accurate +type NotificationReport struct { + // ForMe determines whether this value is for the user. If it is + // false, this report may be ignored. + ForMe bool + // Type is the type of notification. The list can be seen + Type string + // Source is the source of the notification. + Source []byte } -// NotificationsForMe Check if a notification received is for me -// It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller, -// a Type, and a source. These are as follows: -// TYPE SOURCE DESCRIPTION -// "default" recipient user ID A message with no association -// "request" sender user ID A channel request has been received -// "reset" sender user ID A channel reset has been received -// "confirm" sender user ID A channel request has been accepted -// "silent" sender user ID A message which should not be notified on -// "e2e" sender user ID reception of an E2E message -// "group" group ID reception of a group chat message -// "endFT" sender user ID Last message sent confirming end of file transfer -// "groupRQ" sender user ID Request from sender to join a group chat -func NotificationsForMe(notifCSV, preimages string) (*ManyNotificationForMeReport, error) { - // 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") +// NotificationsForMe parses the received notification data to determine which +// notifications are for this user. // This returns the JSON-marshalled +// NotificationReports. +// +// Parameters: +// - e2eID - e2e object ID in the tracker +// - notificationCSV - the notification data received from the +// notifications' server. +// +// Returns: +// - []byte - A JSON marshalled NotificationReports. Some NotificationReport's +// within in this structure may have their NotificationReport.ForMe +// set to false. These may be ignored. +func NotificationsForMe(e2eId int, notificationCSV string) ([]byte, error) { + // Retrieve user + user, err := e2eTrackerSingleton.get(e2eId) + if err != nil { + return nil, err } - list, err := notifications.DecodeNotificationsCSV(notifCSV) + // Retrieve the services for this user + serviceMap := user.api.GetCmix().GetServices() + services := serviceMap[*user.api.GetReceptionIdentity().ID] + + // Decode notifications' server data + notificationList, err := notifications.DecodeNotificationsCSV(notificationCSV) if err != nil { return nil, err } - notifList := make([]*NotificationForMeReport, len(list)) + // Construct a report list + reportList := make([]*NotificationReport, len(notificationList)) - for i, notifData := range list { - notifList[i] = &NotificationForMeReport{ - ForMe: false, - Type: "", - Source: nil, - } - // check if any preimages match with the passed in data - for _, preimage := range preimageList { - if fingerprint.CheckIdentityFpFromMessageHash(notifData.IdentityFP, notifData.MessageHash, preimage.Data) { - notifList[i] = &NotificationForMeReport{ + // Iterate over data provided by server + for i := range notificationList { + notifData := notificationList[i] + + // Iterate over all services + for j := range services { + // Pull data from services and from notification data + service := services[j] + messageHash := notifData.MessageHash + hash := service.HashFromMessageHash(notifData.MessageHash) + + // Check if this notification data is recognized by + // this service, ie "ForMe" + if service.ForMeFromMessageHash(messageHash, hash) { + // Fill report list with service data + reportList[i] = &NotificationReport{ ForMe: true, - Type: preimage.Type, - Source: preimage.Source, + Type: service.Tag, + Source: service.Identifier, } - break } } } - return &ManyNotificationForMeReport{notifList}, nil -}*/ + return json.Marshal(reportList) +} // RegisterForNotifications allows a client to register for push notifications. // The token is a firebase messaging token. diff --git a/bindings/notifications_test.go b/bindings/notifications_test.go new file mode 100644 index 000000000..390062f08 --- /dev/null +++ b/bindings/notifications_test.go @@ -0,0 +1,28 @@ +package bindings + +import ( + "encoding/json" + "fmt" + "gitlab.com/elixxir/client/e2e/ratchet" + "gitlab.com/xx_network/primitives/id" + "testing" +) + +func TestNotificationReport(t *testing.T) { + reports := []NotificationReport{} + + for i := 0; i < 3; i++ { + nr := NotificationReport{ + ForMe: true, + Type: ratchet.E2e, + Source: id.NewIdFromUInt(uint64(i), id.User, t).Bytes(), + } + + reports = append(reports, nr) + } + + nrs := NotificationReports(reports) + + marshal, _ := json.Marshal(nrs) + fmt.Printf("%s\n", marshal) +} diff --git a/cmix/interface.go b/cmix/interface.go index a5db8b3cf..ef7afecce 100644 --- a/cmix/interface.go +++ b/cmix/interface.go @@ -176,6 +176,9 @@ type Client interface { // running. Multiple trackTriggers can be registered. TrackServices(tracker message.ServicesTracker) + // GetServices retrieves the message.ServiceList. + GetServices() message.ServiceList + /* === In inProcess ===================================================== */ /* It is possible to receive a message over cMix before the fingerprints or triggers are registered. As a result, when handling fails, messages are diff --git a/cmix/message/handler.go b/cmix/message/handler.go index 6a5baf554..510122ffb 100644 --- a/cmix/message/handler.go +++ b/cmix/message/handler.go @@ -43,6 +43,7 @@ type Handler interface { DeleteService(clientID *id.ID, toDelete Service, response Processor) DeleteClientService(clientID *id.ID) TrackServices(triggerTracker ServicesTracker) + GetServices() ServiceList } type handler struct { diff --git a/cmix/message/serviceTracker.go b/cmix/message/serviceTracker.go index 616f6dfda..972646e42 100644 --- a/cmix/message/serviceTracker.go +++ b/cmix/message/serviceTracker.go @@ -20,13 +20,17 @@ func (sm *ServicesManager) TrackServices(tracker ServicesTracker) { sm.trackers = append(sm.trackers, tracker) } -// triggerServiceTracking triggers the tracking of services. Is it called when a -// service is added or removed. -func (sm *ServicesManager) triggerServiceTracking() { - if len(sm.trackers) == 0 { - return - } +// GetServices retrieves the ServiceList from the ServicesManager. +// This is effectively a serializing of the +// ServicesManager's internal tmap. +func (sm *ServicesManager) GetServices() ServiceList { + sm.Mutex.Lock() + defer sm.Mutex.Unlock() + return sm.getServices() +} +// getServices is the non-thread-safe version of GetServices. +func (sm *ServicesManager) getServices() ServiceList { services := make(ServiceList) for uid, tmap := range sm.tmap { tList := make([]Service, 0, len(tmap)) @@ -35,6 +39,18 @@ func (sm *ServicesManager) triggerServiceTracking() { } services[uid] = tList } + return services +} + +// triggerServiceTracking triggers the tracking of services. Is it called when a +// service is added or removed. +func (sm *ServicesManager) triggerServiceTracking() { + sm.Mutex.Lock() + if len(sm.trackers) == 0 { + return + } + services := sm.GetServices() + sm.Mutex.Unlock() for _, callback := range sm.trackers { go callback(services) -- GitLab