diff --git a/auth/interface.go b/auth/interface.go index ea1b108744ed2fceb201eb6268c2d54d684f8002..b45ba36e9c7d4a135a0f7adf48416b1e1cbf1112 100644 --- a/auth/interface.go +++ b/auth/interface.go @@ -150,5 +150,6 @@ type e2eHandler interface { receiveParams session.Params) (partner.Manager, error) GetPartner(partnerID *id.ID) (partner.Manager, error) DeletePartner(partnerId *id.ID) error + DeletePartnerNotify(partnerId *id.ID, params e2e.Params) error GetReceptionID() *id.ID } diff --git a/auth/state_test.go b/auth/state_test.go index 10ebb2d3ed4d9d99c654da78dfacc260a877a771..34b670453b4a5052d71868ebeccd4d8030f1a511 100644 --- a/auth/state_test.go +++ b/auth/state_test.go @@ -8,6 +8,7 @@ package auth import ( + "gitlab.com/elixxir/client/e2e" "io" "math/rand" "testing" @@ -100,6 +101,9 @@ func (me2e *mockE2E) GetPartner(partnerID *id.ID) (partner.Manager, error) { func (me2e *mockE2E) DeletePartner(partnerId *id.ID) error { return nil } +func (me2e *mockE2E) DeletePartnerNotify(partnerId *id.ID, params e2e.Params) error { + return nil +} func (me2e *mockE2E) GetReceptionID() *id.ID { return me2e.reception } diff --git a/auth/utils_test.go b/auth/utils_test.go index 4c53ff91a95c40e4b138258592cb292b12d8e749..5f27a218e0651736ace0ca165ef44e0ef581b05d 100644 --- a/auth/utils_test.go +++ b/auth/utils_test.go @@ -105,6 +105,10 @@ func (m mockE2eHandler) DeletePartner(partnerId *id.ID) error { return nil } +func (m mockE2eHandler) DeletePartnerNotify(partnerId *id.ID, params e2e.Params) error { + return nil +} + func (m mockE2eHandler) GetAllPartnerIDs() []*id.ID { return nil } @@ -139,6 +143,18 @@ func (m mockE2eHandler) GetReceptionID() *id.ID { return nil } +func (m mockE2eHandler) RegisterCallbacks(callbacks e2e.Callbacks) { + panic("implement me") +} + +func (m mockE2eHandler) AddPartnerCallbacks(partnerID *id.ID, cb e2e.Callbacks) { + panic("implement me") +} + +func (m mockE2eHandler) DeletePartnerCallbacks(partnerID *id.ID) { + panic("implement me") +} + type mockSentRequestHandler struct{} func (msrh *mockSentRequestHandler) Add(sr *store.SentRequest) {} diff --git a/catalog/messageTypes.go b/catalog/messageTypes.go index 6fc09ead3c9520f5db635ebf29da7e160698cc00..9ab1244631e1c6265e2f8a4a205022f35d943ac0 100644 --- a/catalog/messageTypes.go +++ b/catalog/messageTypes.go @@ -32,6 +32,10 @@ const ( // to confirm completion of a rekey. For ephemeral only e2e instances. KeyExchangeConfirmEphemeral = 33 + // E2eClose message is sent when a user deletes a partner and wants to + // inform their partner that the connection is closed. + E2eClose MessageType = 34 + /* Group chat message types */ // GroupCreationRequest - A group chat request message sent to all members in a group. @@ -65,6 +69,8 @@ func (mt MessageType) String() string { return "KeyExchangeTriggerEphemeral" case KeyExchangeConfirmEphemeral: return "KeyExchangeConfirmEphemeral" + case E2eClose: + return "E2eClose" case GroupCreationRequest: return "GroupCreationRequest" case NewFileTransfer: diff --git a/e2e/callbacks.go b/e2e/callbacks.go new file mode 100644 index 0000000000000000000000000000000000000000..915229bd040440511c21caa0666dea6a68f5950d --- /dev/null +++ b/e2e/callbacks.go @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package e2e + +import ( + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/xx_network/primitives/id" + "sync" +) + +// partnerCallbacks is a thread-safe wrapper for Callbacks specific to partner +// IDs. For E2E operations with a specific partner, these Callbacks will be used +// instead. +type partnerCallbacks struct { + callbacks map[id.ID]Callbacks + sync.RWMutex +} + +// newPartnerCallbacks initializes an empty partnerCallbacks. +func newPartnerCallbacks() *partnerCallbacks { + return &partnerCallbacks{ + callbacks: make(map[id.ID]Callbacks), + } +} + +// add registers Callbacks that override the generic E2E callback for the given +// partner ID. +func (pcb *partnerCallbacks) add(partnerID *id.ID, cbs Callbacks) { + pcb.Lock() + defer pcb.Unlock() + pcb.callbacks[*partnerID] = cbs +} + +// delete deletes the callbacks for the given partner ID. +func (pcb *partnerCallbacks) delete(partnerID *id.ID) { + pcb.Lock() + defer pcb.Unlock() + delete(pcb.callbacks, *partnerID) +} + +// get returns the Callbacks for the given partner ID. +func (pcb *partnerCallbacks) get(partnerID *id.ID) Callbacks { + pcb.RLock() + defer pcb.RUnlock() + + return pcb.callbacks[*partnerID] +} + +// DefaultCallbacks is a simple structure for providing a default Callbacks +// implementation. It should generally not be used. +type DefaultCallbacks struct{} + +func (d *DefaultCallbacks) ConnectionClosed(*id.ID, rounds.Round) { + jww.ERROR.Printf("No valid e2e callback assigned!") +} diff --git a/e2e/callbacks_test.go b/e2e/callbacks_test.go new file mode 100644 index 0000000000000000000000000000000000000000..327020f04304e6b960f3bc72e7c6916f9c61ad7c --- /dev/null +++ b/e2e/callbacks_test.go @@ -0,0 +1,110 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package e2e + +import ( + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/xx_network/primitives/id" + "reflect" + "testing" +) + +// Tests that newPartnerCallbacks returns the expected new partnerCallbacks. +func Test_newPartnerCallbacks(t *testing.T) { + expected := &partnerCallbacks{ + callbacks: make(map[id.ID]Callbacks), + } + + pcb := newPartnerCallbacks() + + if !reflect.DeepEqual(expected, pcb) { + t.Errorf("Did not get expected new partnerCallbacks."+ + "\nexpected: %+v\nreceived: %+v", expected, pcb) + } +} + +// Tests that partnerCallbacks.add adds all the expected callbacks to the map. +func Test_partnerCallbacks_add(t *testing.T) { + pcb := newPartnerCallbacks() + + const n = 10 + expected := make(map[id.ID]Callbacks, n) + for i := uint64(0); i < n; i++ { + expected[*id.NewIdFromUInt(i, id.User, t)] = &mockCallbacks{id: i} + } + + for partnerID, cbs := range expected { + pcb.add(&partnerID, cbs) + } + + if !reflect.DeepEqual(expected, pcb.callbacks) { + t.Errorf("Callback list does not match expected."+ + "\nexpected: %v\nreceived: %v", expected, pcb.callbacks) + } +} + +// Tests that partnerCallbacks.delete removes all callbacks from the map. +func Test_partnerCallbacks_delete(t *testing.T) { + pcb := newPartnerCallbacks() + + const n = 10 + expected := make(map[id.ID]Callbacks, n) + for i := uint64(0); i < n; i++ { + partnerID, cbs := id.NewIdFromUInt(i, id.User, t), &mockCallbacks{id: i} + expected[*partnerID] = cbs + pcb.add(partnerID, cbs) + } + + for partnerID := range expected { + pcb.delete(&partnerID) + } + + if len(pcb.callbacks) > 0 { + t.Errorf("Callback map not empty: %v", pcb.callbacks) + } +} + +// Tests that partnerCallbacks.get returns the expected Callbacks for each +// partner ID. +func Test_partnerCallbacks_get(t *testing.T) { + pcb := newPartnerCallbacks() + + const n = 10 + expected := make(map[id.ID]Callbacks, n) + for i := uint64(0); i < n; i++ { + partnerID, cbs := id.NewIdFromUInt(i, id.User, t), &mockCallbacks{id: i} + expected[*partnerID] = cbs + pcb.add(partnerID, cbs) + } + + for partnerID, expectedCbs := range expected { + cbs := pcb.get(&partnerID) + if !reflect.DeepEqual(expectedCbs, cbs) { + t.Errorf("Callbacks for parter %s do not match."+ + "\nexpected: %+v\nreceived: %+v", &partnerID, expectedCbs, cbs) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Mock Callbacks // +//////////////////////////////////////////////////////////////////////////////// + +// Verify that mockCallbacks adhere to the Callbacks interface +var _ Callbacks = (*mockCallbacks)(nil) + +// mockCallbacks is a structure used for testing that adheres to the Callbacks +// interface. +type mockCallbacks struct { + id uint64 + connectionClosedChan chan *id.ID +} + +func (m *mockCallbacks) ConnectionClosed(partner *id.ID, _ rounds.Round) { + m.connectionClosedChan <- partner +} diff --git a/e2e/interface.go b/e2e/interface.go index c4c6b68af6ccacfc85876ce964120a5868e3e4a2..0578bbc719731f5d1c83a7e16330506aac8bac66 100644 --- a/e2e/interface.go +++ b/e2e/interface.go @@ -1,6 +1,7 @@ package e2e import ( + "gitlab.com/elixxir/client/cmix/rounds" "time" "github.com/cloudflare/circl/dh/sidh" @@ -121,10 +122,15 @@ type Handler interface { // GetPartner returns the partner per its ID, if it exists GetPartner(partnerID *id.ID) (partner.Manager, error) - // DeletePartner removes the associated contact from the E2E store - // myID is your ID in the relationship + // DeletePartner removes the contact associated with the partnerId from the + // E2E store. DeletePartner(partnerId *id.ID) error + // DeletePartnerNotify removes the contact associated with the partnerId + // from the E2E store. It then sends a critical E2E message to the partner + // informing them that the E2E connection is closed. + DeletePartnerNotify(partnerId *id.ID, params Params) error + // GetAllPartnerIDs returns a list of all partner IDs that the user has // an E2E relationship with. GetAllPartnerIDs() []*id.ID @@ -149,6 +155,31 @@ type Handler interface { // RemoveService removes all services for the given tag RemoveService(tag string) error + /* === Callbacks ==================================================== */ + + // The E2E callbacks are a set of callbacks that are called in specific + // situations. For example, ConnectionClosed is called when you receive a + // message from a partner informing you they have deleted the partnership. + // + // By default, on E2E creation, callbacks are not set and no action is + // taken. To set generic callbacks, that is used for all partners, use + // RegisterCallbacks. Specific callbacks can be registered per user that are + // used instead of the generic ones. + + // RegisterCallbacks registers a generic Callbacks. This function overwrites + // any previously saved Callbacks. By default, these callbacks are nil and + // ignored until set via this function. + RegisterCallbacks(callbacks Callbacks) + + // AddPartnerCallbacks registers a new Callbacks that overrides the generic + // E2E callbacks for the given partner ID. + AddPartnerCallbacks(partnerID *id.ID, cb Callbacks) + + // DeletePartnerCallbacks deletes the Callbacks that override the generic + // E2E callback for the given partner ID. Deleting these callbacks will + // result in the generic E2E callbacks being used. + DeletePartnerCallbacks(partnerID *id.ID) + /* === Unsafe ======================================================= */ // SendUnsafe sends a message without encryption. It breaks @@ -198,3 +229,12 @@ type Handler interface { // message PayloadSize() uint } + +// Callbacks contains the possible callbacks on E2E. +type Callbacks interface { + // ConnectionClosed is called when you receive a message from a partner + // informing you that they have deleted the partnership and will no longer + // receive messages. It is called when a catalog.E2eClose E2E message is + // received. + ConnectionClosed(partner *id.ID, round rounds.Round) +} diff --git a/e2e/manager.go b/e2e/manager.go index 00630b7467ad57d1eb21a653b6a3315061f79c24..3bebd7dae8595cb20285dac8abcb509f7b811965 100644 --- a/e2e/manager.go +++ b/e2e/manager.go @@ -1,7 +1,10 @@ package e2e import ( + "bytes" + "encoding/base64" "encoding/json" + "sync" "time" jww "github.com/spf13/jwalterweatherman" @@ -37,6 +40,14 @@ type manager struct { crit *critical rekeyParams rekey.Params kv *versioned.KV + + // Generic Callbacks for all E2E operations; by default this is nil and + // ignored until set via RegisterCallbacks + callbacks Callbacks + cbMux sync.Mutex + + // Partner-specific Callbacks + partnerCallbacks *partnerCallbacks } const legacyE2EKey = "legacyE2ESystem" @@ -139,13 +150,15 @@ func loadE2E(kv *versioned.KV, net cmix.Client, myDefaultID *id.ID, events event.Reporter) (Handler, error) { m := &manager{ - Switchboard: receive.New(), - net: net, - myID: myDefaultID, - events: events, - grp: grp, - rekeyParams: rekey.Params{}, - kv: kv, + Switchboard: receive.New(), + net: net, + myID: myDefaultID, + events: events, + grp: grp, + rekeyParams: rekey.Params{}, + kv: kv, + callbacks: nil, + partnerCallbacks: newPartnerCallbacks(), } var err error @@ -166,9 +179,22 @@ func loadE2E(kv *versioned.KV, net cmix.Client, myDefaultID *id.ID, "Failed to unmarshal rekeyParams data") } + // Register listener that calls the ConnectionClosed callback when a + // catalog.E2eClose message is received + m.Switchboard.RegisterFunc( + "connectionClosing", &id.ZeroUser, catalog.E2eClose, m.closeE2eListener) + return m, nil } +// RegisterCallbacks registers the Callbacks to E2E. This function overwrite any +// previously saved Callbacks. +func (m *manager) RegisterCallbacks(callbacks Callbacks) { + m.cbMux.Lock() + defer m.cbMux.Unlock() + m.callbacks = callbacks +} + func (m *manager) StartProcesses() (stoppable.Stoppable, error) { multi := stoppable.NewMulti("e2eManager") @@ -206,6 +232,120 @@ func (m *manager) StartProcesses() (stoppable.Stoppable, error) { return multi, nil } +// DeletePartner removes the contact associated with the partnerId from the E2E +// store. +func (m *manager) DeletePartner(partnerId *id.ID) error { + err := m.Ratchet.DeletePartner(partnerId) + if err != nil { + return err + } + + m.DeletePartnerCallbacks(partnerId) + + return m.Ratchet.DeletePartner(partnerId) +} + +// DeletePartnerNotify removes the contact associated with the partnerId +// from the E2E store. It then sends a critical E2E message to the partner +// informing them that the E2E connection is closed. +func (m *manager) DeletePartnerNotify(partnerId *id.ID, params Params) error { + // Check if the partner exists + p, err := m.GetPartner(partnerId) + if err != nil { + return err + } + + // Enable critical message sending + params.Critical = true + + // Setting the connection fingerprint as the payload allows the receiver + // to verify that this catalog.E2eClose message is from this specific + // E2E relationship. However, this is not strictly necessary since this + // message should not be received by any future E2E connection with the + // same partner. This is done as a sanity check and to plainly show + // which relationship this message belongs to. + payload := p.ConnectionFingerprint().Bytes() + + // Prepare an E2E message informing the partner that you are closing the E2E + // connection. The send is prepared before deleting the partner because the + // partner needs to be available to build the E2E message. The message is + // not sent first to avoid sending the partner an erroneous message of + // deletion fails. + sendFunc, err := m.prepareSendE2E( + catalog.E2eClose, partnerId, payload, params) + if err != nil { + return err + } + + err = m.Ratchet.DeletePartner(partnerId) + if err != nil { + return err + } + + m.DeletePartnerCallbacks(partnerId) + + // Send closing E2E message + rounds, msgID, timestamp, err := sendFunc() + if err != nil { + jww.ERROR.Printf("Failed to send %s E2E message to %s: %+v", + catalog.E2eClose, partnerId, err) + } else { + jww.INFO.Printf( + "Sent %s E2E message to %s on rounds %v with message ID %s at %s", + catalog.E2eClose, partnerId, rounds, msgID, timestamp) + } + + return nil +} + +// closeE2eListener calls the ConnectionClose callback when a catalog.E2eClose +// message is received from a partner. +func (m *manager) closeE2eListener(item receive.Message) { + p, err := m.GetPartner(item.Sender) + if err != nil { + jww.ERROR.Printf("Could not find sender %s of %s message: %+v", + item.Sender, catalog.E2eClose, err) + return + } + + // Check the connection fingerprint to verify that the message is + // from the expected E2E relationship (refer to the comment in + // DeletePartner for more details) + if !bytes.Equal(p.ConnectionFingerprint().Bytes(), item.Payload) { + jww.ERROR.Printf("Received %s message from %s with incorrect "+ + "connection fingerprint %s.", catalog.E2eClose, item.Sender, + base64.StdEncoding.EncodeToString(item.Payload)) + return + } + + jww.INFO.Printf("Received %s message from %s for relationship %s. "+ + "Calling ConnectionClosed callback.", + catalog.E2eClose, item.Sender, p.ConnectionFingerprint()) + + if cb := m.partnerCallbacks.get(item.Sender); cb != nil { + cb.ConnectionClosed(item.Sender, item.Round) + } else if m.callbacks != nil { + m.cbMux.Lock() + m.callbacks.ConnectionClosed(item.Sender, item.Round) + m.cbMux.Unlock() + } else { + jww.INFO.Printf("No ConnectionClosed callback found.") + } +} + +// AddPartnerCallbacks registers a new Callbacks that overrides the generic +// e2e callbacks for the given partner ID. +func (m *manager) AddPartnerCallbacks(partnerID *id.ID, cb Callbacks) { + m.partnerCallbacks.add(partnerID, cb) +} + +// DeletePartnerCallbacks deletes the Callbacks that override the generic +// e2e callback for the given partner ID. Deleting these callbacks will +// result in the generic e2e callbacks being used. +func (m *manager) DeletePartnerCallbacks(partnerID *id.ID) { + m.partnerCallbacks.delete(partnerID) +} + // EnableUnsafeReception enables the reception of unsafe message by registering // bespoke services for reception. For debugging only! func (m *manager) EnableUnsafeReception() { diff --git a/e2e/sendE2E.go b/e2e/sendE2E.go index 159f7c8451ca997d3924803f01bb30eb4355d0f1..284008a92d92988b56abb6f6e81fd49381be94e0 100644 --- a/e2e/sendE2E.go +++ b/e2e/sendE2E.go @@ -44,15 +44,26 @@ func (m *manager) SendE2E(mt catalog.MessageType, recipient *id.ID, } -func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, - payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) { +// sendE2eFn contains a prepared sendE2E operation and sends an E2E message when +// called, returning the results of the send. +type sendE2eFn func() ([]id.Round, e2e.MessageID, time.Time, error) + +// prepareSendE2E makes a prepared function that does the e2e send. +// This is so that when doing deletePartner we can prepare the send before +// deleting and then send after deleting to ensure there is correctness. +// +// Note: the timestamp in the send is recorded in this call, not when the +// sendE2e function is called. +func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID, + payload []byte, params Params) (sendE2E sendE2eFn, err error) { ts := netTime.Now() + sendFuncs := make([]func(), 0) + partitions, internalMsgId, err := m.partitioner.Partition(recipient, mt, ts, payload) if err != nil { - return nil, e2e.MessageID{}, time.Time{}, - errors.WithMessage(err, "failed to send unsafe message") + return nil, errors.WithMessage(err, "failed to send unsafe message") } jww.INFO.Printf("E2E sending %d messages to %s", len(partitions), recipient) @@ -66,7 +77,7 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, // key negotiated for the ratchet partner, err := m.Ratchet.GetPartner(recipient) if err != nil { - return nil, e2e.MessageID{}, time.Time{}, errors.WithMessagef(err, + return nil, errors.WithMessagef(err, "cannot send E2E message no relationship found with %s", recipient) } @@ -101,7 +112,7 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, keyGetter, params.KeyGetRetryCount, params.KeyGeRetryDelay, params.Stop, recipient, format.DigestContents(p), i) if err != nil { - return nil, e2e.MessageID{}, time.Time{}, errors.WithMessagef(err, + return nil, errors.WithMessagef(err, "Failed to get key for end-to-end encryption") } @@ -123,36 +134,56 @@ func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, // We send each partition in its own thread here; some may send in round // X, others in X+1 or X+2, and so on - wg.Add(1) - go func(i int) { - var err error - roundIds[i], _, err = m.net.Send(recipient, - key.Fingerprint(), s, contentsEnc, mac, params.CMIXParams) - if err != nil { - errCh <- err - } - wg.Done() - }(i) + localI := i + thisSendFunc := func() { + wg.Add(1) + go func(i int) { + var err error + roundIds[i], _, err = m.net.Send(recipient, + key.Fingerprint(), s, contentsEnc, mac, params.CMIXParams) + if err != nil { + errCh <- err + } + wg.Done() + }(localI) + } + sendFuncs = append(sendFuncs, thisSendFunc) } - wg.Wait() - - numFail, errRtn := getSendErrors(errCh) - if numFail > 0 { - jww.INFO.Printf("Failed to E2E send %d/%d to %s", - numFail, len(partitions), recipient) - return nil, e2e.MessageID{}, time.Time{}, errors.Errorf( - "Failed to E2E send %v/%v sub payloads: %s", - numFail, len(partitions), errRtn) - } else { - jww.INFO.Printf("Successfully E2E sent %d/%d to %s", - len(partitions)-numFail, len(partitions), recipient) - } + sendE2E = func() ([]id.Round, e2e.MessageID, time.Time, error) { + for i := range sendFuncs { + sendFuncs[i]() + } - jww.INFO.Printf("Successful E2E Send of %d messages to %s with msgID %s", - len(partitions), recipient, msgID) + wg.Wait() - return roundIds, msgID, ts, nil + numFail, errRtn := getSendErrors(errCh) + if numFail > 0 { + jww.INFO.Printf("Failed to E2E send %d/%d to %s", + numFail, len(partitions), recipient) + return nil, e2e.MessageID{}, time.Time{}, errors.Errorf( + "Failed to E2E send %v/%v sub payloads: %s", + numFail, len(partitions), errRtn) + } else { + jww.INFO.Printf("Successfully E2E sent %d/%d to %s", + len(partitions)-numFail, len(partitions), recipient) + } + + jww.INFO.Printf("Successful E2E Send of %d messages to %s with msgID %s", + len(partitions), recipient, msgID) + + return roundIds, msgID, ts, nil + } + return sendE2E, nil +} + +func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID, + payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) { + sendFunc, err := m.prepareSendE2E(mt, recipient, payload, params) + if err != nil { + return nil, e2e.MessageID{}, time.Time{}, err + } + return sendFunc() } // waitForKey waits the designated amount of time for a key to become available diff --git a/fileTransfer/e2e/utils_test.go b/fileTransfer/e2e/utils_test.go index 633483607275fb4e2714ccc67f1852a22c1d78a8..164d155740c789916f1ff5a96b3bd3c89079c61f 100644 --- a/fileTransfer/e2e/utils_test.go +++ b/fileTransfer/e2e/utils_test.go @@ -288,24 +288,28 @@ func (m *mockE2e) UnregisterUserListeners(*id.ID) { panic("implement me") } func (m *mockE2e) AddPartner(*id.ID, *cyclic.Int, *cyclic.Int, *sidh.PublicKey, *sidh.PrivateKey, session.Params, session.Params) (partner.Manager, error) { panic("implement me") } -func (m *mockE2e) GetPartner(*id.ID) (partner.Manager, error) { panic("implement me") } -func (m *mockE2e) DeletePartner(*id.ID) error { panic("implement me") } -func (m *mockE2e) GetAllPartnerIDs() []*id.ID { panic("implement me") } -func (m *mockE2e) HasAuthenticatedChannel(*id.ID) bool { panic("implement me") } -func (m *mockE2e) AddService(string, message.Processor) error { panic("implement me") } -func (m *mockE2e) RemoveService(string) error { panic("implement me") } +func (m *mockE2e) GetPartner(*id.ID) (partner.Manager, error) { panic("implement me") } +func (m *mockE2e) DeletePartner(*id.ID) error { panic("implement me") } +func (m *mockE2e) DeletePartnerNotify(*id.ID, e2e.Params) error { panic("implement me") } +func (m *mockE2e) GetAllPartnerIDs() []*id.ID { panic("implement me") } +func (m *mockE2e) HasAuthenticatedChannel(*id.ID) bool { panic("implement me") } +func (m *mockE2e) AddService(string, message.Processor) error { panic("implement me") } +func (m *mockE2e) RemoveService(string) error { panic("implement me") } func (m *mockE2e) SendUnsafe(catalog.MessageType, *id.ID, []byte, e2e.Params) ([]id.Round, time.Time, error) { panic("implement me") } -func (m *mockE2e) EnableUnsafeReception() { panic("implement me") } -func (m *mockE2e) GetGroup() *cyclic.Group { panic("implement me") } -func (m *mockE2e) GetHistoricalDHPubkey() *cyclic.Int { panic("implement me") } -func (m *mockE2e) GetHistoricalDHPrivkey() *cyclic.Int { panic("implement me") } -func (m *mockE2e) GetReceptionID() *id.ID { panic("implement me") } -func (m *mockE2e) FirstPartitionSize() uint { panic("implement me") } -func (m *mockE2e) SecondPartitionSize() uint { panic("implement me") } -func (m *mockE2e) PartitionSize(uint) uint { panic("implement me") } -func (m *mockE2e) PayloadSize() uint { panic("implement me") } +func (m *mockE2e) EnableUnsafeReception() { panic("implement me") } +func (m *mockE2e) GetGroup() *cyclic.Group { panic("implement me") } +func (m *mockE2e) GetHistoricalDHPubkey() *cyclic.Int { panic("implement me") } +func (m *mockE2e) GetHistoricalDHPrivkey() *cyclic.Int { panic("implement me") } +func (m *mockE2e) GetReceptionID() *id.ID { panic("implement me") } +func (m *mockE2e) FirstPartitionSize() uint { panic("implement me") } +func (m *mockE2e) SecondPartitionSize() uint { panic("implement me") } +func (m *mockE2e) PartitionSize(uint) uint { panic("implement me") } +func (m *mockE2e) PayloadSize() uint { panic("implement me") } +func (m *mockE2e) RegisterCallbacks(e2e.Callbacks) { panic("implement me") } +func (m *mockE2e) AddPartnerCallbacks(*id.ID, e2e.Callbacks) { panic("implement me") } +func (m *mockE2e) DeletePartnerCallbacks(*id.ID) { panic("implement me") } //////////////////////////////////////////////////////////////////////////////// // Mock Storage Session // diff --git a/groupChat/e2eManager_test.go b/groupChat/e2eManager_test.go index cc66b2b7dfd6e0c6b4d489fd4a710974250ddd57..37e4019cb8442cf95c8537cbb412d2d4a5d1e585 100644 --- a/groupChat/e2eManager_test.go +++ b/groupChat/e2eManager_test.go @@ -97,6 +97,16 @@ func (*testE2eManager) AddService(string, message.Processor) error { // Unused & unimplemented methods of the test object //////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// +func (tnm *testE2eManager) DeletePartner(partnerId *id.ID) error { + // TODO implement me + panic("implement me") +} + +func (tnm *testE2eManager) DeletePartnerNotify(partnerId *id.ID, params clientE2E.Params) error { + // TODO implement me + panic("implement me") +} + func (*testE2eManager) GetDefaultHistoricalDHPubkey() *cyclic.Int { panic("implement me") } @@ -130,8 +140,18 @@ func (tnm *testE2eManager) UnregisterUserListeners(userID *id.ID) { panic("implement me") } -func (tnm *testE2eManager) DeletePartner(partnerId *id.ID) error { - //TODO implement me +func (tnm *testE2eManager) RegisterCallbacks(callbacks clientE2E.Callbacks) { + // TODO implement me + panic("implement me") +} + +func (tnm *testE2eManager) AddPartnerCallbacks(partnerID *id.ID, cb clientE2E.Callbacks) { + // TODO implement me + panic("implement me") +} + +func (tnm *testE2eManager) DeletePartnerCallbacks(partnerID *id.ID) { + // TODO implement me panic("implement me") } diff --git a/ud/mockE2e_test.go b/ud/mockE2e_test.go index 3be9c745ab0e44bfe413029872e3c9d28d007df6..28ddaeb0badeab3fd2eb0d18e0cacaba137927e0 100644 --- a/ud/mockE2e_test.go +++ b/ud/mockE2e_test.go @@ -99,6 +99,11 @@ func (m mockE2e) GetStorage() storage.Session { type mockE2eHandler struct{} +func (m mockE2eHandler) RegisterCallbacks(callbacks e2e.Callbacks) { + // TODO implement me + panic("implement me") +} + func (m mockE2eHandler) StartProcesses() (stoppable.Stoppable, error) { //TODO implement me panic("implement me") @@ -145,7 +150,22 @@ func (m mockE2eHandler) GetPartner(partnerID *id.ID) (partner.Manager, error) { } func (m mockE2eHandler) DeletePartner(partnerId *id.ID) error { - //TODO implement me + // TODO implement me + panic("implement me") +} + +func (tnm mockE2eHandler) DeletePartnerNotify(partnerId *id.ID, params e2e.Params) error { + // TODO implement me + panic("implement me") +} + +func (m mockE2eHandler) AddPartnerCallbacks(partnerID *id.ID, cb e2e.Callbacks) { + // TODO implement me + panic("implement me") +} + +func (m mockE2eHandler) DeletePartnerCallbacks(partnerID *id.ID) { + // TODO implement me panic("implement me") } diff --git a/xxdk/e2e.go b/xxdk/e2e.go index 722195e9aa47fce2726ad01a19f36db3a9738411..4c2b9e22ca2e800e77489f69690092e13f6db6bc 100644 --- a/xxdk/e2e.go +++ b/xxdk/e2e.go @@ -320,6 +320,34 @@ func (m *E2e) DeleteContact(partnerId *id.ID) error { return nil } +// DeleteContactNotify removes a partner from E2e's storage and sends an E2E +// message to the contact notifying them. +func (m *E2e) DeleteContactNotify(partnerId *id.ID, params e2e.Params) error { + jww.DEBUG.Printf("Deleting contact with ID %s", partnerId) + + _, err := m.e2e.GetPartner(partnerId) + if err != nil { + return errors.WithMessagef(err, "Could not delete %s because "+ + "they could not be found", partnerId) + } + + if err = m.e2e.DeletePartnerNotify(partnerId, params); err != nil { + return err + } + + m.backup.TriggerBackup("contact deleted") + + // FIXME: Do we need this? + // c.e2e.Conversations().Delete(partnerId) + + // call delete requests to make sure nothing is lingering. + // this is for safety to ensure the contact can be re-added + // in the future + _ = m.auth.DeleteRequest(partnerId) + + return nil +} + // MakeAuthCallbacksAdapter creates an authCallbacksAdapter. func MakeAuthCallbacksAdapter(ac AuthCallbacks, e2e *E2e) *authCallbacksAdapter { return &authCallbacksAdapter{