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