github.com/status-im/status-go@v1.1.0/protocol/messenger_identity_image_test.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "errors" 7 "fmt" 8 "testing" 9 "time" 10 11 "github.com/cenkalti/backoff/v3" 12 "github.com/stretchr/testify/require" 13 "github.com/stretchr/testify/suite" 14 "go.uber.org/zap" 15 16 gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" 17 "github.com/status-im/status-go/eth-node/crypto" 18 "github.com/status-im/status-go/eth-node/types" 19 "github.com/status-im/status-go/images" 20 "github.com/status-im/status-go/multiaccounts" 21 "github.com/status-im/status-go/multiaccounts/settings" 22 "github.com/status-im/status-go/params" 23 "github.com/status-im/status-go/protocol/protobuf" 24 "github.com/status-im/status-go/protocol/requests" 25 "github.com/status-im/status-go/protocol/tt" 26 "github.com/status-im/status-go/waku" 27 ) 28 29 func TestMessengerProfilePictureHandlerSuite(t *testing.T) { 30 suite.Run(t, new(MessengerProfilePictureHandlerSuite)) 31 } 32 33 type MessengerProfilePictureHandlerSuite struct { 34 suite.Suite 35 alice *Messenger // client instance of Messenger 36 bob *Messenger // server instance of Messenger 37 38 // If one wants to send messages between different instances of Messenger, 39 // a single Waku service should be shared. 40 shh types.Waku 41 logger *zap.Logger 42 } 43 44 func (s *MessengerProfilePictureHandlerSuite) SetupSuite() { 45 s.logger = tt.MustCreateTestLogger() 46 47 // Setup Waku things 48 config := waku.DefaultConfig 49 config.MinimumAcceptedPoW = 0 50 wakuLogger := s.logger.Named("Waku") 51 shh := waku.New(&config, wakuLogger) 52 s.shh = gethbridge.NewGethWakuWrapper(shh) 53 s.Require().NoError(shh.Start()) 54 } 55 56 func (s *MessengerProfilePictureHandlerSuite) TearDownSuite() { 57 _ = gethbridge.GetGethWakuFrom(s.shh).Stop() 58 _ = s.logger.Sync() 59 } 60 61 func (s *MessengerProfilePictureHandlerSuite) newMessenger(name string) *Messenger { 62 m, err := newTestMessenger(s.shh, testMessengerConfig{ 63 logger: s.logger.Named(fmt.Sprintf("messenger-%s", name)), 64 name: name, 65 extraOptions: []Option{ 66 WithAppSettings(newTestSettings(), params.NodeConfig{}), 67 }, 68 }) 69 s.Require().NoError(err) 70 71 _, err = m.Start() 72 s.Require().NoError(err) 73 74 return m 75 } 76 77 func (s *MessengerProfilePictureHandlerSuite) SetupTest() { 78 // Generate Alice Messenger 79 s.alice = s.newMessenger("Alice") 80 s.bob = s.newMessenger("Bobby") 81 82 // Setup MultiAccount for Alice Messenger 83 s.setupMultiAccount(s.alice) 84 } 85 86 func (s *MessengerProfilePictureHandlerSuite) TearDownTest() { 87 // Shutdown messengers 88 TearDownMessenger(&s.Suite, s.alice) 89 s.alice = nil 90 TearDownMessenger(&s.Suite, s.bob) 91 s.bob = nil 92 _ = s.logger.Sync() 93 } 94 95 func (s *MessengerProfilePictureHandlerSuite) setupMultiAccount(m *Messenger) { 96 name, err := m.settings.DisplayName() 97 s.Require().NoError(err) 98 99 keyUID := m.IdentityPublicKeyString() 100 m.account = &multiaccounts.Account{ 101 Name: name, 102 KeyUID: keyUID, 103 } 104 105 err = m.multiAccounts.SaveAccount(*m.account) 106 s.NoError(err) 107 } 108 109 func (s *MessengerProfilePictureHandlerSuite) generateAndStoreIdentityImages(m *Messenger) map[string]images.IdentityImage { 110 keyUID := m.IdentityPublicKeyString() 111 iis := images.SampleIdentityImages() 112 113 err := m.multiAccounts.StoreIdentityImages(keyUID, iis, false) 114 s.Require().NoError(err) 115 116 out := make(map[string]images.IdentityImage) 117 118 for _, ii := range iis { 119 out[ii.Name] = ii 120 } 121 122 s.Require().Contains(out, images.SmallDimName) 123 s.Require().Contains(out, images.LargeDimName) 124 125 return out 126 } 127 128 func (s *MessengerProfilePictureHandlerSuite) TestChatIdentity() { 129 iis := s.generateAndStoreIdentityImages(s.alice) 130 ci, err := s.alice.createChatIdentity(privateChat) 131 s.Require().NoError(err) 132 s.Require().Exactly(len(iis), len(ci.Images)) 133 } 134 135 func (s *MessengerProfilePictureHandlerSuite) TestEncryptDecryptIdentityImagesWithContactPubKeys() { 136 smPayload := "hello small image" 137 lgPayload := "hello large image" 138 139 ci := protobuf.ChatIdentity{ 140 Clock: uint64(time.Now().Unix()), 141 Images: map[string]*protobuf.IdentityImage{ 142 "small": { 143 Payload: []byte(smPayload), 144 }, 145 "large": { 146 Payload: []byte(lgPayload), 147 }, 148 }, 149 } 150 151 // Make contact keys and Contacts, set the Contacts to added 152 contactKeys := make([]*ecdsa.PrivateKey, 10) 153 for i := range contactKeys { 154 contactKey, err := crypto.GenerateKey() 155 s.Require().NoError(err) 156 contactKeys[i] = contactKey 157 158 contact, err := BuildContactFromPublicKey(&contactKey.PublicKey) 159 s.Require().NoError(err) 160 161 contact.ContactRequestLocalState = ContactRequestStateSent 162 163 s.alice.allContacts.Store(contact.ID, contact) 164 } 165 166 // Test EncryptIdentityImagesWithContactPubKeys 167 err := EncryptIdentityImagesWithContactPubKeys(ci.Images, s.alice) 168 s.Require().NoError(err) 169 170 for _, ii := range ci.Images { 171 s.Require().Equal(s.alice.allContacts.Len(), len(ii.EncryptionKeys)) 172 } 173 s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload) 174 s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload) 175 s.Require().True(ci.Images["small"].Encrypted) 176 s.Require().True(ci.Images["large"].Encrypted) 177 178 // Test DecryptIdentityImagesWithIdentityPrivateKey 179 err = DecryptIdentityImagesWithIdentityPrivateKey(ci.Images, contactKeys[2], &s.alice.identity.PublicKey) 180 s.Require().NoError(err) 181 182 s.Require().Equal(smPayload, string(ci.Images["small"].Payload)) 183 s.Require().Equal(lgPayload, string(ci.Images["large"].Payload)) 184 s.Require().False(ci.Images["small"].Encrypted) 185 s.Require().False(ci.Images["large"].Encrypted) 186 187 // RESET Messenger identity, Contacts and IdentityImage.EncryptionKeys 188 s.alice.allContacts = new(contactMap) 189 ci.Images["small"].EncryptionKeys = nil 190 ci.Images["large"].EncryptionKeys = nil 191 192 // Test EncryptIdentityImagesWithContactPubKeys with no contacts 193 err = EncryptIdentityImagesWithContactPubKeys(ci.Images, s.alice) 194 s.Require().NoError(err) 195 196 for _, ii := range ci.Images { 197 s.Require().Equal(0, len(ii.EncryptionKeys)) 198 } 199 s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload) 200 s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload) 201 s.Require().True(ci.Images["small"].Encrypted) 202 s.Require().True(ci.Images["large"].Encrypted) 203 204 // Test DecryptIdentityImagesWithIdentityPrivateKey with no valid identity 205 err = DecryptIdentityImagesWithIdentityPrivateKey(ci.Images, contactKeys[2], &s.alice.identity.PublicKey) 206 s.Require().NoError(err) 207 208 s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload) 209 s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload) 210 s.Require().True(ci.Images["small"].Encrypted) 211 s.Require().True(ci.Images["large"].Encrypted) 212 } 213 214 func (s *MessengerProfilePictureHandlerSuite) TestPictureInPrivateChatOneSided() { 215 err := s.bob.settings.SaveSettingField(settings.ProfilePicturesVisibility, settings.ProfilePicturesShowToEveryone) 216 s.Require().NoError(err) 217 218 err = s.alice.settings.SaveSettingField(settings.ProfilePicturesVisibility, settings.ProfilePicturesShowToEveryone) 219 s.Require().NoError(err) 220 221 bChat := CreateOneToOneChat(s.alice.IdentityPublicKeyString(), s.alice.IdentityPublicKey(), s.alice.transport) 222 err = s.bob.SaveChat(bChat) 223 s.Require().NoError(err) 224 225 _, err = s.bob.Join(bChat) 226 s.Require().NoError(err) 227 228 // Alice sends a message to the public chat 229 message := buildTestMessage(*bChat) 230 response, err := s.bob.SendChatMessage(context.Background(), message) 231 s.Require().NoError(err) 232 s.Require().NotNil(response) 233 234 options := func(b *backoff.ExponentialBackOff) { 235 b.MaxElapsedTime = 2 * time.Second 236 } 237 238 err = tt.RetryWithBackOff(func() error { 239 240 response, err = s.alice.RetrieveAll() 241 if err != nil { 242 return err 243 } 244 s.Require().NotNil(response) 245 246 contacts := response.Contacts 247 s.logger.Debug("RetryWithBackOff contact data", zap.Any("contacts", contacts)) 248 249 if len(contacts) > 0 && len(contacts[0].Images) > 0 { 250 s.logger.Debug("", zap.Any("contacts", contacts)) 251 return nil 252 } 253 254 return errors.New("no new contacts with images received") 255 }, options) 256 } 257 258 func (s *MessengerProfilePictureHandlerSuite) TestE2eSendingReceivingProfilePicture() { 259 profilePicShowSettings := []settings.ProfilePicturesShowToType{ 260 settings.ProfilePicturesShowToContactsOnly, 261 settings.ProfilePicturesShowToEveryone, 262 settings.ProfilePicturesShowToNone, 263 } 264 265 profilePicViewSettings := []settings.ProfilePicturesVisibilityType{ 266 settings.ProfilePicturesVisibilityContactsOnly, 267 settings.ProfilePicturesVisibilityEveryone, 268 settings.ProfilePicturesVisibilityNone, 269 } 270 271 isContactFor := map[string][]bool{ 272 "alice": {true, false}, 273 "bob": {true, false}, 274 } 275 276 chatContexts := []ChatContext{ 277 publicChat, 278 privateChat, 279 } 280 281 // TODO see if possible to push each test scenario into a go routine 282 for _, cc := range chatContexts { 283 for _, ss := range profilePicShowSettings { 284 for _, vs := range profilePicViewSettings { 285 for _, ac := range isContactFor["alice"] { 286 for _, bc := range isContactFor["bob"] { 287 args := &e2eArgs{ 288 chatContext: cc, 289 showToType: ss, 290 visibilityType: vs, 291 aliceContact: ac, 292 bobContact: bc, 293 } 294 s.Run(args.TestCaseName(s.T()), func() { 295 s.testE2eSendingReceivingProfilePicture(args) 296 }) 297 } 298 } 299 } 300 } 301 } 302 303 s.SetupTest() 304 } 305 306 func (s *MessengerProfilePictureHandlerSuite) testE2eSendingReceivingProfilePicture(args *e2eArgs) { 307 // Generate Alice Messenger 308 alice := s.newMessenger("Alice") 309 bob := s.newMessenger("Bobby") 310 311 // Setup MultiAccount for Alice Messenger 312 s.setupMultiAccount(alice) 313 314 defer func() { 315 TearDownMessenger(&s.Suite, alice) 316 alice = nil 317 TearDownMessenger(&s.Suite, bob) 318 bob = nil 319 _ = s.logger.Sync() 320 }() 321 322 s.logger.Info("testing with criteria:", zap.Any("args", args)) 323 defer s.logger.Info("Completed testing with criteria:", zap.Any("args", args)) 324 325 expectPicture, err := args.resultExpected() 326 s.Require().NoError(err) 327 328 s.logger.Debug("expect to receive a profile pic?", 329 zap.Bool("result", expectPicture), 330 zap.Error(err)) 331 332 // Setting up Bob 333 err = bob.settings.SaveSettingField(settings.ProfilePicturesVisibility, args.visibilityType) 334 s.Require().NoError(err) 335 336 if args.bobContact { 337 _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: alice.IdentityPublicKeyString()}) 338 s.Require().NoError(err) 339 } 340 341 // Create Bob's chats 342 switch args.chatContext { 343 case publicChat: 344 // Bob opens up the public chat and joins it 345 bChat := CreatePublicChat("status", alice.transport) 346 err = bob.SaveChat(bChat) 347 s.Require().NoError(err) 348 349 _, err = bob.Join(bChat) 350 s.Require().NoError(err) 351 case privateChat: 352 bChat := CreateOneToOneChat(alice.IdentityPublicKeyString(), alice.IdentityPublicKey(), alice.transport) 353 err = bob.SaveChat(bChat) 354 s.Require().NoError(err) 355 356 _, err = bob.Join(bChat) 357 s.Require().NoError(err) 358 default: 359 s.Failf("unexpected chat context type", "%s", string(args.chatContext)) 360 } 361 362 // Setting up Alice 363 err = alice.settings.SaveSettingField(settings.ProfilePicturesShowTo, args.showToType) 364 s.Require().NoError(err) 365 366 if args.aliceContact { 367 _, err = alice.AddContact(context.Background(), &requests.AddContact{ID: bob.IdentityPublicKeyString()}) 368 s.Require().NoError(err) 369 } 370 371 iis := s.generateAndStoreIdentityImages(alice) 372 373 // Create chats 374 var aChat *Chat 375 switch args.chatContext { 376 case publicChat: 377 // Alice opens creates a public chat 378 aChat = CreatePublicChat("status", alice.transport) 379 err = alice.SaveChat(aChat) 380 s.Require().NoError(err) 381 382 // Alice sends a message to the public chat 383 message := buildTestMessage(*aChat) 384 response, err := alice.SendChatMessage(context.Background(), message) 385 s.Require().NoError(err) 386 s.Require().NotNil(response) 387 s.Require().Len(response.messages, 1) 388 389 case privateChat: 390 aChat = CreateOneToOneChat(bob.IdentityPublicKeyString(), bob.IdentityPublicKey(), bob.transport) 391 err = alice.SaveChat(aChat) 392 s.Require().NoError(err) 393 394 _, err = alice.Join(aChat) 395 s.Require().NoError(err) 396 397 err = alice.publishContactCode() 398 s.Require().NoError(err) 399 400 default: 401 s.Failf("unexpected chat context type", "%s", string(args.chatContext)) 402 } 403 404 // Poll bob to see if he got the chatIdentity 405 // Retrieve ChatIdentity 406 var contacts []*Contact 407 408 options := func(b *backoff.ExponentialBackOff) { 409 b.MaxElapsedTime = 2 * time.Second 410 } 411 412 err = tt.RetryWithBackOff(func() error { 413 response, err := bob.RetrieveAll() 414 if err != nil { 415 return err 416 } 417 418 contacts = response.Contacts 419 if len(contacts) > 0 && len(contacts[0].Images) > 0 { 420 return nil 421 } 422 423 return errors.New("no new contacts with images received") 424 }, options) 425 426 if !expectPicture { 427 s.Require().EqualError(err, "no new contacts with images received") 428 return 429 } 430 431 s.Require().NoError(err) 432 s.Require().NotNil(contacts) 433 434 // Check if alice's contact data with profile picture is there 435 var contact *Contact 436 for _, c := range contacts { 437 if c.ID == alice.IdentityPublicKeyString() { 438 contact = c 439 } 440 } 441 s.Require().NotNil(contact) 442 443 // Check that Bob now has Alice's profile picture(s) 444 switch args.chatContext { 445 case publicChat: 446 // In public chat context we only need the images.SmallDimName, but also may have the large 447 s.Require().GreaterOrEqual(len(contact.Images), 1) 448 s.Require().Contains(contact.Images, images.SmallDimName) 449 s.Require().Equal(iis[images.SmallDimName].Payload, contact.Images[images.SmallDimName].Payload) 450 451 case privateChat: 452 s.Require().Equal(len(contact.Images), 2) 453 s.Require().Contains(contact.Images, images.SmallDimName) 454 s.Require().Contains(contact.Images, images.LargeDimName) 455 s.Require().Equal(iis[images.SmallDimName].Payload, contact.Images[images.SmallDimName].Payload) 456 s.Require().Equal(iis[images.LargeDimName].Payload, contact.Images[images.LargeDimName].Payload) 457 } 458 } 459 460 type e2eArgs struct { 461 chatContext ChatContext 462 showToType settings.ProfilePicturesShowToType 463 visibilityType settings.ProfilePicturesVisibilityType 464 aliceContact bool 465 bobContact bool 466 } 467 468 func (args *e2eArgs) String() string { 469 return fmt.Sprintf("ChatContext: %s, ShowTo: %s, Visibility: %s, AliceContact: %t, BobContact: %t", 470 string(args.chatContext), 471 profilePicShowSettingsMap[args.showToType], 472 profilePicViewSettingsMap[args.visibilityType], 473 args.aliceContact, 474 args.bobContact, 475 ) 476 } 477 478 func (args *e2eArgs) TestCaseName(t *testing.T) string { 479 expected, err := args.resultExpected() 480 require.NoError(t, err) 481 482 return fmt.Sprintf("%s-%s-%s-ac.%t-bc.%t-exp.%t", 483 string(args.chatContext), 484 profilePicShowSettingsMap[args.showToType], 485 profilePicViewSettingsMap[args.visibilityType], 486 args.aliceContact, 487 args.bobContact, 488 expected, 489 ) 490 } 491 492 func (args *e2eArgs) resultExpected() (bool, error) { 493 switch args.showToType { 494 case settings.ProfilePicturesShowToContactsOnly: 495 if args.aliceContact { 496 return args.resultExpectedVS() 497 } 498 return false, nil 499 case settings.ProfilePicturesShowToEveryone: 500 return args.resultExpectedVS() 501 case settings.ProfilePicturesShowToNone: 502 return false, nil 503 default: 504 return false, errors.New("unknown ProfilePicturesShowToType") 505 } 506 } 507 508 func (args *e2eArgs) resultExpectedVS() (bool, error) { 509 switch args.visibilityType { 510 case settings.ProfilePicturesVisibilityContactsOnly: 511 return true, nil 512 case settings.ProfilePicturesVisibilityEveryone: 513 return true, nil 514 case settings.ProfilePicturesVisibilityNone: 515 // If we are contacts, we save the image regardless 516 return args.bobContact, nil 517 default: 518 return false, errors.New("unknown ProfilePicturesVisibilityType") 519 } 520 } 521 522 var profilePicShowSettingsMap = map[settings.ProfilePicturesShowToType]string{ 523 settings.ProfilePicturesShowToContactsOnly: "ShowToContactsOnly", 524 settings.ProfilePicturesShowToEveryone: "ShowToEveryone", 525 settings.ProfilePicturesShowToNone: "ShowToNone", 526 } 527 528 var profilePicViewSettingsMap = map[settings.ProfilePicturesVisibilityType]string{ 529 settings.ProfilePicturesVisibilityContactsOnly: "ViewFromContactsOnly", 530 settings.ProfilePicturesVisibilityEveryone: "ViewFromEveryone", 531 settings.ProfilePicturesVisibilityNone: "ViewFromNone", 532 }