From 8c90353bc074b9a6a6093f465f77f4cb2f9b1f8d Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Mon, 22 May 2023 16:22:33 +0400 Subject: [PATCH] fix: create a CR on contact sync with received CR state (#3495) * fix: create a CR on contact sync with received CR state * fix: create a CR on contact sync with sent CR state * Review fixes * Fix: ignore own contact installation or syncing --- protocol/messenger_contact_requests_test.go | 202 ++++++++++++++++++-- protocol/messenger_contacts.go | 6 +- protocol/messenger_handler.go | 73 ++++++- 3 files changed, 259 insertions(+), 22 deletions(-) diff --git a/protocol/messenger_contact_requests_test.go b/protocol/messenger_contact_requests_test.go index 0a461a3ad..b5385ea74 100644 --- a/protocol/messenger_contact_requests_test.go +++ b/protocol/messenger_contact_requests_test.go @@ -158,9 +158,9 @@ func (s *MessengerContactRequestSuite) receiveContactRequest(messageText string, return contactRequest } -func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *common.Message, theirMessenger *Messenger) { +func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *common.Message, sender *Messenger, receiver *Messenger) { // Accept contact request, receiver side - resp, err := theirMessenger.AcceptContactRequest(context.Background(), &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequest.ID)}) + resp, err := receiver.AcceptContactRequest(context.Background(), &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequest.ID)}) s.Require().NoError(err) // Make sure the message is updated @@ -187,16 +187,16 @@ func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *comm s.Require().True(resp.Chats()[1].Active) // Make sure the sender is added to our contacts - contacts := theirMessenger.AddedContacts() + contacts := receiver.AddedContacts() s.Require().Len(contacts, 1) // Make sure we consider them a mutual contact, receiver side - mutualContacts := theirMessenger.MutualContacts() + mutualContacts := receiver.MutualContacts() s.Require().Len(mutualContacts, 1) // Wait for the message to reach its destination resp, err = WaitOnMessengerResponse( - s.m, + sender, func(r *MessengerResponse) bool { return len(r.Contacts) == 1 && len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1 }, @@ -240,7 +240,7 @@ func (s *MessengerContactRequestSuite) acceptContactRequest(contactRequest *comm // Receiver's side chat should be also active after the accepting the CR myID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey)) - chat, ok = theirMessenger.allChats.Load(myID) + chat, ok = receiver.allChats.Load(myID) s.Require().True(ok) s.Require().NotNil(chat) s.Require().True(chat.Active) @@ -314,6 +314,28 @@ func (s *MessengerContactRequestSuite) retractContactRequest(contactID string, t s.Require().Equal(ContactRequestStateNone, resp.Contacts[0].ContactRequestRemoteState) } +func (s *MessengerContactRequestSuite) syncInstallationContactV2FromContact(contact *Contact) protobuf.SyncInstallationContactV2 { + return protobuf.SyncInstallationContactV2{ + LastUpdatedLocally: contact.LastUpdatedLocally, + LastUpdated: contact.LastUpdated, + Id: contact.ID, + DisplayName: contact.DisplayName, + EnsName: contact.EnsName, + LocalNickname: contact.LocalNickname, + Added: contact.added(), + Blocked: contact.Blocked, + Muted: false, + HasAddedUs: contact.hasAddedUs(), + Removed: contact.Removed, + ContactRequestLocalState: int64(contact.ContactRequestLocalState), + ContactRequestRemoteState: int64(contact.ContactRequestRemoteState), + ContactRequestRemoteClock: int64(contact.ContactRequestRemoteClock), + ContactRequestLocalClock: int64(contact.ContactRequestLocalClock), + VerificationStatus: int64(contact.VerificationStatus), + TrustStatus: int64(contact.TrustStatus), + } +} + func (s *MessengerContactRequestSuite) TestReceiveAndAcceptContactRequest() { //nolint: unused messageText := "hello!" @@ -328,7 +350,7 @@ func (s *MessengerContactRequestSuite) TestReceiveAndAcceptContactRequest() { // } s.sendContactRequest(request, s.m) contactRequest := s.receiveContactRequest(messageText, theirMessenger) - s.acceptContactRequest(contactRequest, theirMessenger) + s.acceptContactRequest(contactRequest, s.m, theirMessenger) } func (s *MessengerContactRequestSuite) TestReceiveAndDismissContactRequest() { @@ -364,7 +386,7 @@ func (s *MessengerContactRequestSuite) TestReceiveAcceptAndRetractContactRequest } s.sendContactRequest(request, s.m) contactRequest := s.receiveContactRequest(messageText, theirMessenger) - s.acceptContactRequest(contactRequest, theirMessenger) + s.acceptContactRequest(contactRequest, s.m, theirMessenger) s.retractContactRequest(contactID, theirMessenger) } @@ -382,7 +404,7 @@ func (s *MessengerContactRequestSuite) TestReceiveAndAcceptContactRequestTwice() } s.sendContactRequest(request, s.m) contactRequest := s.receiveContactRequest(messageText, theirMessenger) - s.acceptContactRequest(contactRequest, theirMessenger) + s.acceptContactRequest(contactRequest, s.m, theirMessenger) // Resend contact request with higher clock value resp, err := s.m.SendContactRequest(context.Background(), request) @@ -540,7 +562,7 @@ func (s *MessengerContactRequestSuite) TestPairedDevicesRemoveContact() { } s.sendContactRequest(request, alice1) contactRequest := s.receiveContactRequest(messageText, bob) - s.acceptContactRequest(contactRequest, bob) + s.acceptContactRequest(contactRequest, alice1, bob) // Wait for the message to reach its destination resp, err := WaitOnMessengerResponse( @@ -608,7 +630,7 @@ func (s *MessengerContactRequestSuite) TestAliceRecoverStateSendContactRequest() s.Require().NotNil(contactRequest) // Bob accepts the contact request - s.acceptContactRequest(contactRequest, bob) + s.acceptContactRequest(contactRequest, alice1, bob) // Alice resets her device alice2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil) @@ -678,7 +700,7 @@ func (s *MessengerContactRequestSuite) TestAliceRecoverStateReceiveContactReques s.Require().NotNil(contactRequest) // Bob accepts the contact request - s.acceptContactRequest(contactRequest, bob) + s.acceptContactRequest(contactRequest, alice1, bob) // Alice resets her device alice2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil) @@ -757,7 +779,7 @@ func (s *MessengerContactRequestSuite) TestAliceOfflineRetractsAndAddsCorrectOrd s.Require().NotNil(contactRequest) // Bob accepts the contact request - s.acceptContactRequest(contactRequest, bob) + s.acceptContactRequest(contactRequest, alice1, bob) // Alice removes Bob from contacts _, err = alice1.RetractContactRequest(&requests.RetractContactRequest{ID: types.Hex2Bytes(bob.myHexIdentity())}) @@ -806,7 +828,7 @@ func (s *MessengerContactRequestSuite) TestAliceOfflineRetractsAndAddsWrongOrder s.Require().NotNil(contactRequest) // Bob accepts the contact request - s.acceptContactRequest(contactRequest, bob) + s.acceptContactRequest(contactRequest, alice1, bob) // Alice removes Bob from contacts _, err = alice1.RetractContactRequest(&requests.RetractContactRequest{ID: types.Hex2Bytes(bob.myHexIdentity())}) @@ -856,7 +878,7 @@ func (s *MessengerContactRequestSuite) TestAliceResendsContactRequestAfterRemovi // Bob accepts the contact request contactRequest := s.receiveContactRequest(messageTextFirst, theirMessenger) s.Require().NotNil(contactRequest) - s.acceptContactRequest(contactRequest, theirMessenger) + s.acceptContactRequest(contactRequest, s.m, theirMessenger) // Alice removes Bob from contacts s.retractContactRequest(contactID, theirMessenger) @@ -878,7 +900,7 @@ func (s *MessengerContactRequestSuite) TestAliceResendsContactRequestAfterRemovi // Bob accepts new contact request contactRequest = s.receiveContactRequest(messageTextSecond, theirMessenger) s.Require().NotNil(contactRequest) - s.acceptContactRequest(contactRequest, theirMessenger) + s.acceptContactRequest(contactRequest, s.m, theirMessenger) // Make sure bob and alice are not mutual after sending CR s.Require().Len(s.m.MutualContacts(), 1) @@ -1052,3 +1074,151 @@ func (s *MessengerContactRequestSuite) TestReceiveAcceptAndRetractContactRequest s.Require().Len(contacts, 1) s.Require().Equal(ContactRequestStateReceived, contacts[0].ContactRequestRemoteState) } + +// The scenario tested is as follow: +// 1) Alice sends a contact request to Bob +// 2) Bob receives CR from Alice +// 3) Bob resets his device +// 4) Bob restores Alice's contact from backup, CR is created +// 5) Bob succesefully accepts restored contact request +// 6) Alice get notified properly +func (s *MessengerContactRequestSuite) TestBobRestoresIncomingContactRequestFromSyncInstallationContactV2() { + messageText := "hello, Bobby!" + + alice := s.m + + bob1 := s.newMessenger(s.shh) + _, err := bob1.Start() + s.Require().NoError(err) + + aliceID := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)) + bobID := types.EncodeHex(crypto.FromECDSAPub(&bob1.identity.PublicKey)) + + // Alice sends a contact request to bob + requestFromAlice := &requests.SendContactRequest{ + ID: bobID, + Message: messageText, + } + s.sendContactRequest(requestFromAlice, alice) + + // Bob receives CR from Alice + contactRequest := s.receiveContactRequest(messageText, bob1) + s.Require().NotNil(contactRequest) + + // Bob resets his device + bob2, err := newMessengerWithKey(s.shh, bob1.identity, s.logger, nil) + s.Require().NoError(err) + + _, err = bob2.Start() + s.Require().NoError(err) + + // Get bob perspective of alice for backup + aliceFromBob := bob1.Contacts()[0] + state := bob2.buildMessageState() + + // Restore alice's contact from backup + sync := s.syncInstallationContactV2FromContact(aliceFromBob) + err = bob2.HandleSyncInstallationContact(state, sync) + s.Require().NoError(err) + + // Accept latest CR for a contact + resp, err := bob2.AcceptLatestContactRequestForContact(context.Background(), &requests.AcceptLatestContactRequestForContact{ID: types.Hex2Bytes(aliceID)}) + s.Require().NoError(err) + + // Make sure the message is updated + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + // NOTE: We don't restore CR message + // s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID) + s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState) + + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStateAccepted, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().True(resp.Contacts[0].mutual()) + + // Make sure the sender is added to our contacts + contacts := bob2.AddedContacts() + s.Require().Len(contacts, 1) + + // Make sure we consider them a mutual contact, receiver side + mutualContacts := bob2.MutualContacts() + s.Require().Len(mutualContacts, 1) +} + +// The scenario tested is as follow: +// 1) Alice sends a contact request to Bob +// 2) Bob receives CR from Alice +// 3) Alice resets her device +// 4) Alice restores Bob's contact from backup, CR is created +// 5) Bob accepts contact request +// 6) Alice get notified properly +func (s *MessengerContactRequestSuite) TestAliceRestoresOutgoingContactRequestFromSyncInstallationContactV2() { + messageText := "hello, Bobby!" + + alice1 := s.m + + bob := s.newMessenger(s.shh) + _, err := bob.Start() + s.Require().NoError(err) + + aliceID := types.EncodeHex(crypto.FromECDSAPub(&alice1.identity.PublicKey)) + bobID := types.EncodeHex(crypto.FromECDSAPub(&bob.identity.PublicKey)) + + // Alice sends a contact request to bob + requestFromAlice := &requests.SendContactRequest{ + ID: bobID, + Message: messageText, + } + s.sendContactRequest(requestFromAlice, alice1) + + // Bob receives CR from Alice + contactRequest := s.receiveContactRequest(messageText, bob) + s.Require().NotNil(contactRequest) + + // Bob resets his device + alice2, err := newMessengerWithKey(s.shh, alice1.identity, s.logger, nil) + s.Require().NoError(err) + + _, err = alice2.Start() + s.Require().NoError(err) + + // Get bob perspective of alice for backup + bobFromAlice := alice1.Contacts()[0] + state := alice2.buildMessageState() + + // Restore alice's contact from backup + sync := s.syncInstallationContactV2FromContact(bobFromAlice) + err = alice2.HandleSyncInstallationContact(state, sync) + s.Require().NoError(err) + + // Accept latest CR for a contact + resp, err := bob.AcceptLatestContactRequestForContact(context.Background(), &requests.AcceptLatestContactRequestForContact{ID: types.Hex2Bytes(aliceID)}) + s.Require().NoError(err) + + // Make sure the message is updated + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + // NOTE: We don't restore CR message + // s.Require().Equal(resp.Messages()[0].ID, contactRequest.ID) + s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState) + + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStateAccepted, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().True(resp.Contacts[0].mutual()) + + // Make sure the sender is added to our contacts + contacts := bob.AddedContacts() + s.Require().Len(contacts, 1) + + // Make sure we consider them a mutual contact, receiver side + mutualContacts := bob.MutualContacts() + s.Require().Len(mutualContacts, 1) +} diff --git a/protocol/messenger_contacts.go b/protocol/messenger_contacts.go index 2301f6178..b8cce9df8 100644 --- a/protocol/messenger_contacts.go +++ b/protocol/messenger_contacts.go @@ -475,7 +475,7 @@ func (m *Messenger) AddContact(ctx context.Context, request *requests.AddContact request.Nickname, request.DisplayName, "", - "Please add me to your contacts", + defaultContactRequestText(), false, true, true, @@ -962,6 +962,10 @@ func defaultContactRequestID(contactID string) string { return "0x" + types.Bytes2Hex(append(types.Hex2Bytes(contactID), 0x20)) } +func defaultContactRequestText() string { + return "Please add me to your contacts" +} + func (m *Messenger) BuildContact(request *requests.BuildContact) (*Contact, error) { contact, ok := m.allContacts.Load(request.PublicKey) if !ok { diff --git a/protocol/messenger_handler.go b/protocol/messenger_handler.go index 25d3e14b3..e373cba3f 100644 --- a/protocol/messenger_handler.go +++ b/protocol/messenger_handler.go @@ -275,7 +275,7 @@ func (m *Messenger) createContactRequestForContactUpdate(contact *Contact, messa messageState.CurrentMessageState.Message.Clock, messageState.CurrentMessageState.WhisperTimestamp, contact, - "Please add me to your contacts", + defaultContactRequestText(), false, ) if err != nil { @@ -328,8 +328,8 @@ func (m *Messenger) createIncomingContactRequestNotification(contact *Contact, m Name: contact.PrimaryName(), Message: contactRequest, Type: ActivityCenterNotificationTypeContactRequest, - Author: messageState.CurrentMessageState.Contact.ID, - Timestamp: messageState.CurrentMessageState.WhisperTimestamp, + Author: contactRequest.From, + Timestamp: contactRequest.WhisperTimestamp, ChatID: contact.ID, Read: contactRequest.ContactRequestState == common.ContactRequestStateAccepted || contactRequest.ContactRequestState == common.ContactRequestStateDismissed, Accepted: contactRequest.ContactRequestState == common.ContactRequestStateAccepted, @@ -410,10 +410,59 @@ func (m *Messenger) handleCommandMessage(state *ReceivedMessageState, message *c return nil } +func (m *Messenger) syncContactRequestForInstallationContact(contact *Contact, state *ReceivedMessageState, chat *Chat, outgoing bool) error { + if chat == nil { + return fmt.Errorf("no chat restored during the contact synchronisation, contact.ID = %s", contact.ID) + } + + contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(contact.ID) + if err != nil { + return err + } + + if contactRequestID != "" { + return nil + } + + clock, timestamp := chat.NextClockAndTimestamp(m.transport) + contactRequest, err := m.generateContactRequest(clock, timestamp, contact, defaultContactRequestText(), outgoing) + if err != nil { + return err + } + + contactRequest.ID = defaultContactRequestID(contact.ID) + + state.Response.AddMessage(contactRequest) + err = m.persistence.SaveMessages([]*common.Message{contactRequest}) + if err != nil { + return err + } + + if outgoing { + notification := m.generateOutgoingContactRequestNotification(contact, contactRequest) + err = m.addActivityCenterNotification(state.Response, notification) + if err != nil { + return err + } + } else { + err = m.createIncomingContactRequestNotification(contact, state, contactRequest, true) + if err != nil { + return err + } + } + + return nil +} + func (m *Messenger) HandleSyncInstallationContact(state *ReceivedMessageState, message protobuf.SyncInstallationContactV2) error { + // Ignore own contact installation + if message.Id == m.myHexIdentity() { + return nil + } + removedOrBlocked := message.Removed || message.Blocked chat, ok := state.AllChats.Load(message.Id) - if !ok && (message.Added || message.Muted) && !removedOrBlocked { + if !ok && (message.Added || message.HasAddedUs || message.Muted) && !removedOrBlocked { pubKey, err := common.HexToPubkey(message.Id) if err != nil { return err @@ -448,16 +497,31 @@ func (m *Messenger) HandleSyncInstallationContact(state *ReceivedMessageState, m uint64(message.ContactRequestLocalClock)) state.ModifiedContacts.Store(contact.ID, true) state.AllContacts.Store(contact.ID, contact) + + err := m.syncContactRequestForInstallationContact(contact, state, chat, contact.ContactRequestLocalState == ContactRequestStateSent) + if err != nil { + return err + } } else if message.Added || message.HasAddedUs { // NOTE(cammellos): this is for handling backward compatibility, old clients // won't propagate ContactRequestRemoteClock or ContactRequestLocalClock if message.Added && contact.LastUpdatedLocally < message.LastUpdatedLocally { contact.ContactRequestSent(message.LastUpdatedLocally) + + err := m.syncContactRequestForInstallationContact(contact, state, chat, true) + if err != nil { + return err + } } if message.HasAddedUs && contact.LastUpdated < message.LastUpdated { contact.ContactRequestReceived(message.LastUpdated) + + err := m.syncContactRequestForInstallationContact(contact, state, chat, false) + if err != nil { + return err + } } if message.Removed && contact.LastUpdatedLocally < message.LastUpdatedLocally { @@ -466,7 +530,6 @@ func (m *Messenger) HandleSyncInstallationContact(state *ReceivedMessageState, m return err } } - } // Sync last updated field