From 498f7ad5b76c413062bc8818cfa6bf06adb13ebf Mon Sep 17 00:00:00 2001
From: "Richard T. Carback III" <rick.carback@gmail.com>
Date: Mon, 21 Mar 2022 17:47:13 +0000
Subject: [PATCH] Implement Triggers and Fingerprints manager objects, WIP
 handler2 function implementation.

---
 interfaces/networkManager.go               |  40 +++---
 network/fingerprints.go                    | 131 -------------------
 network/message/fingerprints.go            | 111 +++++++++++++++++
 network/{ => message}/fingerprints_test.go |   0
 network/message/handler.go                 | 105 ++++------------
 network/message/manager.go                 |   7 ++
 network/{ => message}/triggers.go          | 138 ++++++++++-----------
 network/{ => message}/triggers_test.go     |   0
 8 files changed, 236 insertions(+), 296 deletions(-)
 delete mode 100644 network/fingerprints.go
 create mode 100644 network/message/fingerprints.go
 rename network/{ => message}/fingerprints_test.go (100%)
 rename network/{ => message}/triggers.go (55%)
 rename network/{ => message}/triggers_test.go (100%)

diff --git a/interfaces/networkManager.go b/interfaces/networkManager.go
index 34ab130ea..f6feba860 100644
--- a/interfaces/networkManager.go
+++ b/interfaces/networkManager.go
@@ -8,16 +8,18 @@
 package interfaces
 
 import (
+	"time"
+
 	"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/comms/mixmessages"
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
-	"time"
 )
 
 type NetworkManager interface {
@@ -115,12 +117,12 @@ type NetworkManager interface {
 	//   type - a descriptive string of the trigger. Generally used in notifications
 	//   source - a byte buffer of related data. Generally used in notifications.
 	//     Example: Sender ID
-	AddTrigger(trigger Trigger, response MessageProcessorTrigger) error
+	AddTrigger(trigger Trigger, response MessageProcessor) error
 
 	// RemoveTrigger - If only a single response is associated with the preimage, the entire
 	// preimage is removed. If there is more than one response, only the given response is removed
 	// if nil is passed in for response, all triggers for the preimage will be removed
-	RemoveTrigger(preimage []byte, response MessageProcessorTrigger) error
+	RemoveTrigger(preimage Preimage, response MessageProcessor) error
 
 	// TrackTriggers - Registers a callback which will get called every time triggers change.
 	// It will receive the triggers list every time it is modified.
@@ -129,6 +131,8 @@ type NetworkManager interface {
 	TrackTriggers(func(triggers []Trigger))
 }
 
+type Preimage [32]byte
+
 type Identity struct {
 	// Identity
 	EphId  ephemeral.Id
@@ -147,24 +151,30 @@ type IdentityParams struct {
 	EndValid   time.Time // Timestamp when the ephID stops being valid
 
 	// Makes the identity not store on disk
+	// When an ephemeral identity is deleted, all fingerprints & triggers
+	// associated with it also delete.
+	// TODO: This should not be confused with EphID for checking
+	// when messages are for the the user. That's a different type
+	// of Ephemeral in this context.
 	Ephemeral bool
 }
 
 type Trigger struct {
-	Preimage []byte
-	Type     string
-	Source   []byte
-}
-
-type MessageProcessorFP interface {
-	Process(message format.Message, fingerprint format.Fingerprint)
-	MarkFingerprintUsed(fingerprint format.Fingerprint)
+	Preimage
+	Type   string
+	Source []byte
 }
 
-type MessageProcessorTrigger interface {
-	Process(message format.Message, preimage []byte, Type string, source []byte)
-	Equals(trigger MessageProcessorTrigger) bool
-	String() string
+type MessageProcessor interface {
+	// Process decrypts and hands off the message to its internal down
+	// stream message processing system.
+	// CRITICAL: Fingerprints should never be used twice. Process must
+	// denote, in long term storage, usage of a fingerprint and that
+	// fingerprint must not be added again during application load.
+	// It is a security vulnerability to reuse a fingerprint. It leaks
+	// privacy and can lead to compromise of message contents and integrity.
+	Process(message format.Message, receptionID Identity,
+		round *mixmessages.RoundInfo)
 }
 
 //type Ratchet interface {
diff --git a/network/fingerprints.go b/network/fingerprints.go
deleted file mode 100644
index 4c44331bb..000000000
--- a/network/fingerprints.go
+++ /dev/null
@@ -1,131 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 xx network SEZC                                          //
-//                                                                           //
-// Use of this source code is governed by a license that can be found in the //
-// LICENSE file                                                              //
-///////////////////////////////////////////////////////////////////////////////
-
-package network
-
-import (
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/interfaces"
-	"gitlab.com/elixxir/primitives/format"
-	"sync"
-)
-
-// Processor is an object which ties an interfaces.MessageProcessorFP
-// to a lock. This prevents the processor from being used in multiple
-// different threads.
-type Processor struct {
-	interfaces.MessageProcessorFP
-	sync.Mutex
-}
-
-// NewFingerprints is a constructor function for the Processor object.
-func newProcessor(mp interfaces.MessageProcessorFP) *Processor {
-	return &Processor{
-		MessageProcessorFP: mp,
-		Mutex:              sync.Mutex{},
-	}
-}
-
-// Fingerprints is a thread-safe map, mapping format.Fingerprint's to
-// a Processor object.
-type Fingerprints struct {
-	fingerprints map[format.Fingerprint]*Processor
-	sync.RWMutex
-}
-
-// NewFingerprints is a constructor function for the Fingerprints tracker.
-func NewFingerprints() *Fingerprints {
-	return &Fingerprints{
-		fingerprints: make(map[format.Fingerprint]*Processor),
-		RWMutex:      sync.RWMutex{},
-	}
-}
-
-// Get is a thread-safe getter for the Fingerprints map. Get returns the mapped
-// processor and true (representing that it exists in the map) if the provided
-// fingerprint has an entry. Otherwise, Get returns nil and false.
-func (f *Fingerprints) Get(fingerprint format.Fingerprint) (*Processor, bool) {
-	f.RLock()
-	defer f.RUnlock()
-	fp, exists := f.fingerprints[fingerprint]
-	if !exists {
-		return nil, false
-	}
-	return fp, true
-}
-
-// AddFingerprint is a thread-safe setter for the Fingerprints map. AddFingerprint
-// maps the given fingerprint key to the processor value. If there is already
-// an entry for this fingerprint, the method returns with no write operation.
-func (f *Fingerprints) AddFingerprint(fingerprint format.Fingerprint,
-	processor interfaces.MessageProcessorFP) {
-	f.Lock()
-	defer f.Unlock()
-
-	f.addFingerprint(fingerprint, processor)
-
-}
-
-// AddFingerprints is a thread-safe setter for multiple entries into
-// the Fingerprints map. If there is not a 1:1 relationship between
-// fingerprints and  processors slices (i.e. the lengths of these slices
-// are equivalent), an error will be returned.
-// Otherwise, each fingerprint is written to the associated processor.
-// If there is already an entry for the given fingerprint/processor pair,
-// no write operation will occur for this pair.
-func (f *Fingerprints) AddFingerprints(fps []format.Fingerprint,
-	processors []interfaces.MessageProcessorFP) error {
-	f.Lock()
-	defer f.Unlock()
-
-	if len(fps) != len(processors) {
-		return errors.Errorf("Canot perform a batch add when there are "+
-			"not an equal amount of fingerprints and processors. "+
-			"Given %d fingerprints and %d processors.", len(fps), len(processors))
-	}
-
-	for i, fp := range fps {
-		f.addFingerprint(fp, processors[i])
-	}
-
-	return nil
-}
-
-// addFingerprint is a non-thread-safe helper function which writes a Processor
-// to the given fingerprint key. If an entry already exists for this fingerprint key,
-// no write operation occurs.
-func (f *Fingerprints) addFingerprint(fingerprint format.Fingerprint,
-	processor interfaces.MessageProcessorFP) {
-
-	if _, exists := f.fingerprints[fingerprint]; exists {
-		return
-	}
-
-	newMsgProc := newProcessor(processor)
-
-	f.fingerprints[fingerprint] = newMsgProc
-}
-
-// RemoveFingerprint is a thread-safe deletion operation on the Fingerprints map.
-// It will remove the entry for the given fingerprint from the map.
-func (f *Fingerprints) RemoveFingerprint(fingerprint format.Fingerprint) {
-	f.Lock()
-	defer f.Unlock()
-
-	delete(f.fingerprints, fingerprint)
-}
-
-// RemoveFingerprints is a thread-safe batch deletion operation on the Fingerprints map.
-// It will remove the entries for the given fingerprints from the map.
-func (f *Fingerprints) RemoveFingerprints(fingerprint []format.Fingerprint) {
-	f.Lock()
-	defer f.Unlock()
-
-	for _, fp := range fingerprint {
-		delete(f.fingerprints, fp)
-	}
-}
diff --git a/network/message/fingerprints.go b/network/message/fingerprints.go
new file mode 100644
index 000000000..fa4091163
--- /dev/null
+++ b/network/message/fingerprints.go
@@ -0,0 +1,111 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"sync"
+
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// FingerprintsManager is a thread-safe map, mapping format.Fingerprint's to
+// a Processor object.
+type FingerprintsManager struct {
+	fpMap map[id.ID]map[format.Fingerprint]interfaces.MessageProcessor
+	sync.Mutex
+}
+
+// NewFingerprints is a constructor function for the Fingerprints tracker.
+func NewFingerprints() *FingerprintsManager {
+	return &FingerprintsManager{
+		fpMap: make(map[id.ID]map[format.Fingerprint]interfaces.MessageProcessor),
+	}
+}
+
+// Pop returns the associated processor to the fingerprint and removes
+// it from our list.
+// CRITICAL: it is never ok to process a fingerprint twice. This is a security
+// vulnerability.
+func (f *FingerprintsManager) pop(clientID *id.ID,
+	fingerprint format.Fingerprint) (
+	interfaces.MessageProcessor, bool) {
+	f.Lock()
+	defer f.Unlock()
+	cid := *clientID
+	if idFpmap, exists := f.fpMap[cid]; exists {
+		if proc, exists := idFpmap[fingerprint]; exists {
+			delete(f.fpMap[cid], fingerprint)
+			if len(f.fpMap[cid]) == 0 {
+				delete(f.fpMap, cid)
+			}
+			return proc, true
+		}
+	}
+
+	return nil, false
+}
+
+// AddFingerprint is a thread-safe setter for the Fingerprints
+// map. AddFingerprint maps the given fingerprint key to the processor
+// value. If there is already an entry for this fingerprint, the
+// method returns with no write operation.
+func (f *FingerprintsManager) Add(clientID *id.ID,
+	fingerprint format.Fingerprint,
+	mp interfaces.MessageProcessor) error {
+	f.Lock()
+	defer f.Unlock()
+
+	cid := *clientID
+
+	if _, exists := f.fpMap[cid]; !exists {
+		f.fpMap[cid] = make(
+			map[format.Fingerprint]interfaces.MessageProcessor)
+	}
+
+	if _, exists := f.fpMap[cid][fingerprint]; exists {
+		return errors.Errorf("fingerprint %s already exists",
+			fingerprint)
+	}
+
+	f.fpMap[cid][fingerprint] = mp
+	return nil
+}
+
+// Delete is a thread-safe deletion operation on the Fingerprints map.
+// It will remove the entry for the given fingerprint from the map.
+func (f *FingerprintsManager) Delete(clientID *id.ID,
+	fingerprint format.Fingerprint) {
+	f.Lock()
+	defer f.Unlock()
+
+	cid := *clientID
+
+	if _, exists := f.fpMap[cid]; exists {
+		if _, exists = f.fpMap[cid][fingerprint]; exists {
+			delete(f.fpMap[cid], fingerprint)
+		}
+		if len(f.fpMap[cid]) == 0 {
+			delete(f.fpMap, cid)
+		}
+	}
+}
+
+// DeleteClient is a thread-safe deletion operation on the Fingerprints map.
+// It will remove all entres for the given clientID from the map.
+func (f *FingerprintsManager) DeleteClient(clientID *id.ID,
+	fingerprint format.Fingerprint) {
+	f.Lock()
+	defer f.Unlock()
+
+	cid := *clientID
+
+	delete(f.fpMap, cid)
+}
diff --git a/network/fingerprints_test.go b/network/message/fingerprints_test.go
similarity index 100%
rename from network/fingerprints_test.go
rename to network/message/fingerprints_test.go
diff --git a/network/message/handler.go b/network/message/handler.go
index 5f72f8641..9ffb92bab 100644
--- a/network/message/handler.go
+++ b/network/message/handler.go
@@ -9,7 +9,10 @@ package message
 
 import (
 	"fmt"
+	"time"
+
 	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"
@@ -19,7 +22,6 @@ import (
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
-	"time"
 )
 
 func (m *Manager) handleMessages(stop *stoppable.Single) {
@@ -162,95 +164,35 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle, edge *edge
 	}
 }
 
-
-func (m *Manager) handleMessage2(ecrMsg format.Message, bundle Bundle) {
-	fingerprint := ecrMsg.GetKeyFP()
-	msgDigest := ecrMsg.Digest()
-	identity := bundle.Identity
-
-	round := bundle.RoundInfo
-	newID := // todo use new id systme from ticket
-		{
-		ID id.ID
-		ephID ephemeral.Id
-	}
-
-	// If we have a fingerprint, process it.
-	messageProc, exists := m.fingerprints.Pop(fingerprint) {
-		// note scope here is all broken, fix...
-		m.fingers.Lock()
-		defer m.fingersUnlock()
-		mp, ok := m.fingers[fingerprint]
-		if ok {
-			mp.MarkFingerprintUsed(fingerprint)
-			delete(m.fingers, fingerprint)
-			return mp, true
-		}
-		return nil, false
-	}
-	if exists {
-		// in progress is a future project. 
-		// m.inprogress.Add({fingerprint, ecrMsg, newID, round})
-		go messageProc.Process(ecrMsg, newID, round)
-		return
-	}
-
-	triggerProc, trigger, exists := m.triggers.Lookup(
-		ecrMsg.GetIdentityFP(), ecrMsgContents)
-	if exists {
-		go triggerProc.Process(ecrMsg, newID, round, trigger)
-		return
-	} else {
-		// TODO: delete this else block because it should not be needed.
-		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(),
-		// 	ecrMsgContents, preimage.MakeDefault(identity.Source))
-	}
-
-	if jww.GetLogThreshold() == jww.LevelTrace {
-		expectedFP := fingerprint2.IdentityFP(ecrMsgContents,
-			preimage.MakeDefault(identity.Source))
-		jww.TRACE.Printf("Message for %d (%s) failed identity "+
-			"check: %v (expected-default) vs %v (received)",
-			identity.EphId,
-			identity.Source, expectedFP, ecrMsg.GetIdentityFP())
-	}
-	im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, round: %d"+
-		"msgDigest: %s, not determined to be for client", ecrMsg.GetKeyFP(), bundle.Round, ecrMsg.Digest())
-	m.Internal.Events.Report(1, "MessageReception", "Garbled", im)
-	m.Session.GetGarbledMessages().Add(ecrMsg)
-}
-
 func (m *Manager) handleMessage2(ecrMsg format.Message, bundle Bundle) {
 	fingerprint := ecrMsg.GetKeyFP()
-	msgDigest := ecrMsg.Digest()
+	// msgDigest := ecrMsg.Digest()
 	identity := bundle.Identity
 
 	round := bundle.RoundInfo
-	newID := // todo use new id systme from ticket
-		{
-		ID id.ID
-		ephID ephemeral.Id
-	}
+	// newID := *id.ID{} // todo use new id systme from ticket
+	// 	{
+	// 	ID id.ID
+	// 	ephID ephemeral.Id
+	// }
+	var receptionID interfaces.Identity
 
 	// If we have a fingerprint, process it.
-	//Lock
-	messageProc, fpLock, exists := m.fingerprints[fingerprint]
-	// Unlock
-	if exists {
-		fpLock.Lock()
-		defer fpLock.Unlock()
-		messageProc.MarkFingerprintUsed(fingerprint)
-		messageProc.Process(ecrMsg, newID, round)
-		delete(m.fingerprints, fingerprint)
+	if proc, exists := m.pop(fingerprint); exists {
+		proc.Process(ecrMsg, receptionID, round)
 		return
 	}
 
-	triggerProc, trigger, exists := m.triggers.Lookup(
-		ecrMsg.GetIdentityFP(), ecrMsgContents)
+	triggers, exists := m.get(ecrMsg.GetIdentityFP(), ecrMsg.GetContents())
 	if exists {
-		go triggerProc.Process(ecrMsg, newID, round, trigger)
+		for _, t := range triggers {
+			go t.Process(ecrMsg, receptionID, round)
+		}
+		if len(triggers) == 0 {
+			jww.ERROR.Printf("empty trigger list for %s",
+				ecrMsg.GetIdentityFP()) // get preimage
+			)
+		}
 		return
 	} else {
 		// TODO: delete this else block because it should not be needed.
@@ -261,7 +203,7 @@ func (m *Manager) handleMessage2(ecrMsg format.Message, bundle Bundle) {
 	}
 
 	if jww.GetLogThreshold() == jww.LevelTrace {
-		expectedFP := fingerprint2.IdentityFP(ecrMsgContents,
+		expectedFP := fingerprint2.IdentityFP(ecrMsg.GetContents(),
 			preimage.MakeDefault(identity.Source))
 		jww.TRACE.Printf("Message for %d (%s) failed identity "+
 			"check: %v (expected-default) vs %v (received)",
@@ -269,7 +211,8 @@ func (m *Manager) handleMessage2(ecrMsg format.Message, bundle Bundle) {
 			identity.Source, expectedFP, ecrMsg.GetIdentityFP())
 	}
 	im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, round: %d"+
-		"msgDigest: %s, not determined to be for client", ecrMsg.GetKeyFP(), bundle.Round, ecrMsg.Digest())
+		"msgDigest: %s, not determined to be for client",
+		ecrMsg.GetKeyFP(), bundle.Round, ecrMsg.Digest())
 	m.Internal.Events.Report(1, "MessageReception", "Garbled", im)
 	m.Session.GetGarbledMessages().Add(ecrMsg)
 }
diff --git a/network/message/manager.go b/network/message/manager.go
index 0ac5eeceb..a9422cc50 100644
--- a/network/message/manager.go
+++ b/network/message/manager.go
@@ -10,6 +10,7 @@ package message
 import (
 	"encoding/base64"
 	"fmt"
+
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/interfaces/params"
 	"gitlab.com/elixxir/client/network/gateway"
@@ -31,6 +32,9 @@ type Manager struct {
 	nodeRegistration chan network.NodeGateway
 	networkIsHealthy chan bool
 	triggerGarbled   chan struct{}
+
+	FingerprintsManager
+	TriggersManager
 }
 
 func NewManager(internal internal.Internal, param params.Network,
@@ -54,6 +58,9 @@ func NewManager(internal internal.Internal, param params.Network,
 		}
 		m.blacklistedNodes[string(decodedId)] = nil
 	}
+
+	m.FingerprintsManager = *NewFingerprints()
+	m.TriggersManager = *NewTriggers()
 	return &m
 }
 
diff --git a/network/triggers.go b/network/message/triggers.go
similarity index 55%
rename from network/triggers.go
rename to network/message/triggers.go
index 33da8982e..453c45cc5 100644
--- a/network/triggers.go
+++ b/network/message/triggers.go
@@ -5,14 +5,15 @@
 // LICENSE file                                                              //
 ///////////////////////////////////////////////////////////////////////////////
 
-package network
+package message
 
 import (
-	"encoding/base64"
+	"sync"
+
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/client/interfaces"
-	fingerprint2 "gitlab.com/elixxir/crypto/fingerprint"
-	"sync"
+	"gitlab.com/elixxir/crypto/fingerprint"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 /* Trigger - predefined hash based tags appended to all cmix messages
@@ -35,17 +36,17 @@ Triggers are ephemeral to the session. When starting a new client, all triggers
 re-added before StartNetworkFollower is called.
 */
 
-type Triggers struct {
-	triggers map[string][]*Trigger
-	sync.RWMutex
+type TriggersManager struct {
+	tmap map[id.ID]map[interfaces.Preimage][]trigger
+	sync.Mutex
 }
 
-type Trigger struct {
+type trigger struct {
 	interfaces.Trigger
-	interfaces.MessageProcessorTrigger
+	interfaces.MessageProcessor
 }
 
-func NewTriggers() *Triggers {
+func NewTriggers() *TriggersManager {
 	// todo: implement me
 	return nil
 }
@@ -64,23 +65,21 @@ func NewTriggers() *Triggers {
 //  - privatizing the state-changing methods
 //  - leaking lookup on this layer and migrating the state modifiation methods
 //    a layer down in a sepearate package
-func (t *Triggers) Lookup(receivedIdentityFp,
-	ecrMsgContents []byte) (triggers []*Trigger, forMe bool) {
-	t.RLock()
-	defer t.RUnlock()
-
-	for preimage, triggerList := range t.triggers {
-		preimageBytes, err := unmarshalPreimage(preimage)
-		if err != nil {
-			// fixme: panic here?, An error here would mean there's a bad
-			//  key-value pair in the map (specifically the preimage-key is bad,
-			//  as it should be base64 encoded).
-		}
-		// fixme, there probably needs to be a small refactor.
-		//  Terminology and variable names are being used misused. For example:
-		//  phrases like tag, preimage and identityFP are being used
-		//  interchangeably in the code and it's getting unwieldy.
-		if fingerprint2.CheckIdentityFP(receivedIdentityFp, ecrMsgContents, preimageBytes) {
+func (t *TriggersManager) get(clientID *id.ID, receivedIdentityFp,
+	ecrMsgContents []byte) ([]trigger,
+	bool) {
+	t.Lock()
+	defer t.Unlock()
+	cid := *clientID
+
+	triggers, exists := t.tmap[cid]
+	if !exists {
+		return nil, false
+	}
+
+	for pi, triggerList := range triggers {
+		if fingerprint.CheckIdentityFP(receivedIdentityFp,
+			ecrMsgContents, pi[:]) {
 			return triggerList, true
 		}
 	}
@@ -95,77 +94,78 @@ func (t *Triggers) Lookup(receivedIdentityFp,
 //   type - a descriptive string of the trigger. Generally used in notifications
 //   source - a byte buffer of related data. Generally used in notifications.
 //     Example: Sender ID
-func (t *Triggers) Add(trigger interfaces.Trigger,
-	response interfaces.MessageProcessorTrigger) error {
+func (t *TriggersManager) Add(clientID *id.ID, newTrigger interfaces.Trigger,
+	response interfaces.MessageProcessor) {
 	t.Lock()
 	defer t.Unlock()
 
-	marshalledPreimage := marshalPreimage(trigger.Preimage)
+	newEntry := trigger{
+		Trigger:          newTrigger,
+		MessageProcessor: response,
+	}
 
-	newTrigger := &Trigger{
-		Trigger:                 trigger,
-		MessageProcessorTrigger: response,
+	cid := *clientID
+	if _, exists := t.tmap[cid]; !exists {
+		t.tmap[cid] = make(map[interfaces.Preimage][]trigger)
 	}
 
-	if existingTriggers, exists := t.triggers[marshalledPreimage]; exists {
-		// fixme Should there be a check if this response exists already?
-		t.triggers[marshalledPreimage] = append(existingTriggers, newTrigger)
-		return nil
+	pi := newTrigger.Preimage
+	if existingTriggers, exists := t.tmap[cid][pi]; exists {
+		t.tmap[cid][pi] = append(existingTriggers, newEntry)
 	}
 
-	t.triggers[marshalledPreimage] = []*Trigger{newTrigger}
+	t.tmap[cid][pi] = []trigger{newEntry}
 
-	return nil
 }
 
-// RemoveTrigger - If only a single response is associated with the preimage,
+// Delete - If only a single response is associated with the preimage,
 // the entire preimage is removed. If there is more than one response, only
 // the given response is removed. If nil is passed in for response,
 // all triggers for the preimage will be removed.
-func (t *Triggers) RemoveTrigger(preimage []byte,
-	response interfaces.MessageProcessorTrigger) error {
+func (t *TriggersManager) Delete(clientID *id.ID, preimage interfaces.Preimage,
+	response interfaces.MessageProcessor) error {
 	t.Lock()
 	defer t.Unlock()
 
-	marshalledPreimage := marshalPreimage(preimage)
+	if response == nil {
+		return errors.Errorf("response cannot be nil when deleting")
+	}
 
-	triggers, exists := t.triggers[marshalledPreimage]
+	cid := *clientID
+
+	idTmap, exists := t.tmap[cid]
 	if !exists {
-		return errors.Errorf("No trigger with preimage %q found",
-			marshalledPreimage)
+		return nil
 	}
 
-	if response == nil {
-		delete(t.triggers, marshalledPreimage)
+	triggers, exists := idTmap[preimage]
+	if !exists {
 		return nil
 	}
 
-	for _, trigger := range triggers {
-		if trigger.Equals(response) {
-			delete(t.triggers, marshalPreimage(trigger.Preimage))
-			return nil
+	if len(triggers) == 1 && triggers[0].MessageProcessor == response {
+		if len(idTmap) == 1 {
+			delete(t.tmap, cid)
+		} else {
+			delete(t.tmap[cid], preimage)
 		}
 	}
 
-	return errors.Errorf("No response (%q) exists with preimage %q",
-		response.String(), marshalledPreimage)
-}
-
-// fixme: maybe make preimage a type or struct and place this in primitives?
+	for idx, cur := range triggers {
+		if cur.MessageProcessor == response {
+			t.tmap[cid][preimage] = append(triggers[:idx],
+				triggers[idx+1:]...)
+			return nil
+		}
+	}
 
-// marshalPreimage is a helper which encodes the preimage byte data to
-// a base64 encoded string.
-func marshalPreimage(pi []byte) string {
-	return base64.StdEncoding.EncodeToString(pi)
+	return nil
 }
 
-// unmarshalPreimage is a helper which decodes the preimage base64 string to
-// bytes.
-func unmarshalPreimage(data string) ([]byte, error) {
-	decoded, err := base64.StdEncoding.DecodeString(data)
-	if err != nil {
-		return nil, err
-	}
+// DeleteClient - delete the mapping associated with an ID
+func (t *TriggersManager) DeleteClient(clientID *id.ID) {
+	t.Lock()
+	defer t.Unlock()
 
-	return decoded, nil
+	delete(t.tmap, *clientID)
 }
diff --git a/network/triggers_test.go b/network/message/triggers_test.go
similarity index 100%
rename from network/triggers_test.go
rename to network/message/triggers_test.go
-- 
GitLab