diff --git a/VERSION b/VERSION index 22a3057d2..46448c71b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.56.9 +0.57.0 diff --git a/protocol/common/message_processor.go b/protocol/common/message_processor.go index 946f7bca7..a0138f3e2 100644 --- a/protocol/common/message_processor.go +++ b/protocol/common/message_processor.go @@ -18,6 +18,7 @@ import ( "github.com/status-im/status-go/protocol/datasync" datasyncpeer "github.com/status-im/status-go/protocol/datasync/peer" "github.com/status-im/status-go/protocol/encryption" + "github.com/status-im/status-go/protocol/encryption/sharedsecret" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/transport" v1protocol "github.com/status-im/status-go/protocol/v1" @@ -60,6 +61,9 @@ type MessageProcessor struct { scheduledMessagesSubscriptions []chan<- *RawMessage featureFlags FeatureFlags + + // handleSharedSecrets is a callback that is called every time a new shared secret is negotiated + handleSharedSecrets func([]*sharedsecret.Secret) error } func NewMessageProcessor( @@ -110,9 +114,14 @@ func (p *MessageProcessor) Stop() { for _, c := range p.sentMessagesSubscriptions { close(c) } + p.sentMessagesSubscriptions = nil p.datasync.Stop() // idempotent op } +func (p *MessageProcessor) SetHandleSharedSecrets(handler func([]*sharedsecret.Secret) error) { + p.handleSharedSecrets = handler +} + // SendPrivate takes encoded data, encrypts it and sends through the wire. func (p *MessageProcessor) SendPrivate( ctx context.Context, @@ -203,7 +212,7 @@ func (p *MessageProcessor) sendPrivate( } else if rawMessage.SkipEncryption { // When SkipEncryption is set we don't pass the message to the encryption layer messageIDs := [][]byte{messageID} - hash, newMessage, err := p.sendRawMessage(ctx, recipient, wrappedMessage, messageIDs) + hash, newMessage, err := p.sendPrivateRawMessage(ctx, recipient, wrappedMessage, messageIDs) if err != nil { return nil, errors.Wrap(err, "failed to send a message spec") } @@ -216,6 +225,16 @@ func (p *MessageProcessor) sendPrivate( return nil, errors.Wrap(err, "failed to encrypt message") } + // The shared secret needs to be handle before we send a message + // otherwise the topic might not be set up before we receive a message + if p.handleSharedSecrets != nil { + err := p.handleSharedSecrets([]*sharedsecret.Secret{messageSpec.SharedSecret}) + if err != nil { + return nil, err + } + + } + messageIDs := [][]byte{messageID} hash, newMessage, err := p.sendMessageSpec(ctx, recipient, messageSpec, messageIDs) if err != nil { @@ -305,11 +324,23 @@ func (p *MessageProcessor) SendPublic( return nil, errors.Wrap(err, "failed to wrap message") } - newMessage := &types.NewMessage{ - TTL: whisperTTL, - Payload: wrappedMessage, - PowTarget: calculatePoW(wrappedMessage), - PowTime: whisperPoWTime, + var newMessage *types.NewMessage + if !rawMessage.SkipEncryption { + messageSpec, err := p.protocol.BuildPublicMessage(p.identity, wrappedMessage) + if err != nil { + return nil, errors.Wrap(err, "failed to wrap a public message in the encryption layer") + } + newMessage, err = MessageSpecToWhisper(messageSpec) + if err != nil { + return nil, err + } + } else { + newMessage = &types.NewMessage{ + TTL: whisperTTL, + Payload: wrappedMessage, + PowTarget: calculatePoW(wrappedMessage), + PowTime: whisperPoWTime, + } } messageID := v1protocol.MessageID(&rawMessage.Sender.PublicKey, wrappedMessage) @@ -479,6 +510,16 @@ func (p *MessageProcessor) sendDataSync(ctx context.Context, publicKey *ecdsa.Pu return errors.Wrap(err, "failed to encrypt message") } + // The shared secret needs to be handle before we send a message + // otherwise the topic might not be set up before we receive a message + if p.handleSharedSecrets != nil { + err := p.handleSharedSecrets([]*sharedsecret.Secret{messageSpec.SharedSecret}) + if err != nil { + return err + } + + } + hash, newMessage, err := p.sendMessageSpec(ctx, publicKey, messageSpec, messageIDs) if err != nil { return err @@ -489,8 +530,8 @@ func (p *MessageProcessor) sendDataSync(ctx context.Context, publicKey *ecdsa.Pu return nil } -// sendRawMessage sends a message not wrapped in an encryption layer -func (p *MessageProcessor) sendRawMessage(ctx context.Context, publicKey *ecdsa.PublicKey, payload []byte, messageIDs [][]byte) ([]byte, *types.NewMessage, error) { +// sendPrivateRawMessage sends a message not wrapped in an encryption layer +func (p *MessageProcessor) sendPrivateRawMessage(ctx context.Context, publicKey *ecdsa.PublicKey, payload []byte, messageIDs [][]byte) ([]byte, *types.NewMessage, error) { newMessage := &types.NewMessage{ TTL: whisperTTL, Payload: payload, @@ -517,11 +558,11 @@ func (p *MessageProcessor) sendMessageSpec(ctx context.Context, publicKey *ecdsa var hash []byte - switch { - case messageSpec.SharedSecret != nil: + // process shared secret + if messageSpec.AgreedSecret { logger.Debug("sending using shared secret") - hash, err = p.transport.SendPrivateWithSharedSecret(ctx, newMessage, publicKey, messageSpec.SharedSecret) - default: + hash, err = p.transport.SendPrivateWithSharedSecret(ctx, newMessage, publicKey, messageSpec.SharedSecret.Key) + } else { logger.Debug("sending partitioned topic") hash, err = p.transport.SendPrivateWithPartitioned(ctx, newMessage, publicKey) } diff --git a/protocol/common/message_processor_test.go b/protocol/common/message_processor_test.go index 59705a6f6..9fab926ee 100644 --- a/protocol/common/message_processor_test.go +++ b/protocol/common/message_processor_test.go @@ -17,8 +17,6 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/encryption" - "github.com/status-im/status-go/protocol/encryption/multidevice" - "github.com/status-im/status-go/protocol/encryption/sharedsecret" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/sqlite" transport "github.com/status-im/status-go/protocol/transport/whisper" @@ -63,15 +61,9 @@ func (s *MessageProcessorSuite) SetupTest() { database, err := sqlite.Open(filepath.Join(s.tmpDir, "processor-test.sql"), "some-key") s.Require().NoError(err) - onNewInstallations := func([]*multidevice.Installation) {} - onNewSharedSecret := func([]*sharedsecret.Secret) {} - onSendContactCode := func(*encryption.ProtocolMessageSpec) {} encryptionProtocol := encryption.New( database, "installation-1", - onNewInstallations, - onNewSharedSecret, - onSendContactCode, s.logger, ) @@ -206,9 +198,6 @@ func (s *MessageProcessorSuite) TestHandleDecodedMessagesDatasyncEncrypted() { senderEncryptionProtocol := encryption.New( senderDatabase, "installation-2", - func([]*multidevice.Installation) {}, - func([]*sharedsecret.Secret) {}, - func(*encryption.ProtocolMessageSpec) {}, s.logger, ) diff --git a/protocol/encryption/encryption_multi_device_test.go b/protocol/encryption/encryption_multi_device_test.go index 4d23ad37e..e8517ff83 100644 --- a/protocol/encryption/encryption_multi_device_test.go +++ b/protocol/encryption/encryption_multi_device_test.go @@ -16,7 +16,6 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/protocol/encryption/multidevice" - "github.com/status-im/status-go/protocol/encryption/sharedsecret" ) const ( @@ -65,9 +64,6 @@ func setupUser(user string, s *EncryptionServiceMultiDeviceSuite, n int) error { protocol := New( db, installationID, - func(s []*multidevice.Installation) {}, - func(s []*sharedsecret.Secret) {}, - func(*ProtocolMessageSpec) {}, s.logger.With(zap.String("user", user)), ) s.services[user].services[i] = protocol diff --git a/protocol/encryption/encryption_test.go b/protocol/encryption/encryption_test.go index c60d76a98..63e84b431 100644 --- a/protocol/encryption/encryption_test.go +++ b/protocol/encryption/encryption_test.go @@ -18,9 +18,6 @@ import ( "go.uber.org/zap" "github.com/status-im/status-go/eth-node/crypto" - - "github.com/status-im/status-go/protocol/encryption/multidevice" - "github.com/status-im/status-go/protocol/encryption/sharedsecret" ) var cleartext = []byte("hello") @@ -57,9 +54,6 @@ func (s *EncryptionServiceTestSuite) initDatabases(config encryptorConfig) { db, aliceInstallationID, config, - func(s []*multidevice.Installation) {}, - func(s []*sharedsecret.Secret) {}, - func(*ProtocolMessageSpec) {}, s.logger.With(zap.String("user", "alice")), ) @@ -70,9 +64,6 @@ func (s *EncryptionServiceTestSuite) initDatabases(config encryptorConfig) { db, bobInstallationID, config, - func(s []*multidevice.Installation) {}, - func(s []*sharedsecret.Secret) {}, - func(*ProtocolMessageSpec) {}, s.logger.With(zap.String("user", "bob")), ) } @@ -130,7 +121,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() { // On the receiver side, we should be able to decrypt using our private key and the ephemeral just sent decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID) s.Require().NoError(err) - s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using DH") + s.Equal(cleartext, decryptedPayload1.DecryptedMessage, "It correctly decrypts the payload using DH") // The next message will not be re-using the same key response2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext) @@ -147,7 +138,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() { decryptedPayload2, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response2.Message, defaultMessageID) s.Require().NoError(err) - s.Equal(cleartext, decryptedPayload2, "It correctly decrypts the payload using DH") + s.Equal(cleartext, decryptedPayload2.DecryptedMessage, "It correctly decrypts the payload using DH") } // Alice has Bob's bundle @@ -201,7 +192,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() { // Bob is able to decrypt it using the bundle decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID) s.Require().NoError(err) - s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using X3DH") + s.Equal(cleartext, decryptedPayload1.DecryptedMessage, "It correctly decrypts the payload using X3DH") } // Alice has Bob's bundle @@ -267,7 +258,7 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() { decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID) s.Require().NoError(err) - s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH") + s.Equal(cleartext2, decryptedPayload1.DecryptedMessage, "It correctly decrypts the payload using X3DH") } // Alice has Bob's bundle @@ -351,7 +342,7 @@ func (s *EncryptionServiceTestSuite) TestConversation() { decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID) s.Require().NoError(err) - s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH") + s.Equal(cleartext2, decryptedPayload1.DecryptedMessage, "It correctly decrypts the payload using X3DH") } // Previous implementation allowed max maxSkip keys in the same receiving chain @@ -686,7 +677,7 @@ func receiver( errChan <- err return } - if !reflect.DeepEqual(actualCleartext, cleartext) { + if !reflect.DeepEqual(actualCleartext.DecryptedMessage, cleartext) { errChan <- errors.New("Decrypted value does not match") return } diff --git a/protocol/encryption/protocol.go b/protocol/encryption/protocol.go index ac2d5076c..603e68319 100644 --- a/protocol/encryption/protocol.go +++ b/protocol/encryption/protocol.go @@ -38,7 +38,9 @@ type ProtocolMessageSpec struct { // Installations is the targeted devices Installations []*multidevice.Installation // SharedSecret is a shared secret established among the installations - SharedSecret []byte + SharedSecret *sharedsecret.Secret + // AgreedSecret indicates whether the shared secret has been agreed + AgreedSecret bool // Public means that the spec contains a public wrapped message Public bool } @@ -66,14 +68,11 @@ func (p *ProtocolMessageSpec) PartitionedTopicMode() PartitionTopicMode { } type Protocol struct { - encryptor *encryptor - secret *sharedsecret.SharedSecret - multidevice *multidevice.Multidevice - publisher *publisher.Publisher - - onAddedBundlesHandler func([]*multidevice.Installation) - onNewSharedSecretHandler func([]*sharedsecret.Secret) - onSendContactCodeHandler func(*ProtocolMessageSpec) + encryptor *encryptor + secret *sharedsecret.SharedSecret + multidevice *multidevice.Multidevice + publisher *publisher.Publisher + subscriptions *Subscriptions logger *zap.Logger } @@ -87,18 +86,12 @@ var ( func New( db *sql.DB, installationID string, - addedBundlesHandler func([]*multidevice.Installation), - onNewSharedSecretHandler func([]*sharedsecret.Secret), - onSendContactCodeHandler func(*ProtocolMessageSpec), logger *zap.Logger, ) *Protocol { return NewWithEncryptorConfig( db, installationID, defaultEncryptorConfig(installationID, logger), - addedBundlesHandler, - onNewSharedSecretHandler, - onSendContactCodeHandler, logger, ) } @@ -109,9 +102,6 @@ func NewWithEncryptorConfig( db *sql.DB, installationID string, encryptorConfig encryptorConfig, - addedBundlesHandler func([]*multidevice.Installation), - onNewSharedSecretHandler func([]*sharedsecret.Secret), - onSendContactCodeHandler func(*ProtocolMessageSpec), logger *zap.Logger, ) *Protocol { return &Protocol{ @@ -122,39 +112,36 @@ func NewWithEncryptorConfig( ProtocolVersion: protocolVersion, InstallationID: installationID, }), - publisher: publisher.New(logger), - onAddedBundlesHandler: addedBundlesHandler, - onNewSharedSecretHandler: onNewSharedSecretHandler, - onSendContactCodeHandler: onSendContactCodeHandler, - logger: logger.With(zap.Namespace("Protocol")), + publisher: publisher.New(logger), + logger: logger.With(zap.Namespace("Protocol")), } } -func (p *Protocol) Start(myIdentity *ecdsa.PrivateKey) error { +type Subscriptions struct { + SharedSecrets []*sharedsecret.Secret + SendContactCode <-chan struct{} + Quit chan struct{} +} + +func (p *Protocol) Start(myIdentity *ecdsa.PrivateKey) (*Subscriptions, error) { // Propagate currently cached shared secrets. secrets, err := p.secret.All() if err != nil { - return errors.Wrap(err, "failed to get all secrets") + return nil, errors.Wrap(err, "failed to get all secrets") } - p.onNewSharedSecretHandler(secrets) - - // Handle Publisher system messages. - publisherCh := p.publisher.Start() - - go func() { - for range publisherCh { - messageSpec, err := p.buildContactCodeMessage(myIdentity) - if err != nil { - p.logger.Error("failed to build contact code message", - zap.String("site", "Start"), - zap.Error(err)) - continue - } - - p.onSendContactCodeHandler(messageSpec) - } - }() + p.subscriptions = &Subscriptions{ + SharedSecrets: secrets, + SendContactCode: p.publisher.Start(), + Quit: make(chan struct{}), + } + return p.subscriptions, nil +} +func (p *Protocol) Stop() error { + p.publisher.Stop() + if p.subscriptions != nil { + close(p.subscriptions.Quit) + } return nil } @@ -197,12 +184,6 @@ func (p *Protocol) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload [ return &ProtocolMessageSpec{Message: message, Public: true}, nil } -// buildContactCodeMessage creates a contact code message. It's a public message -// without any data but it carries bundle information. -func (p *Protocol) buildContactCodeMessage(myIdentityKey *ecdsa.PrivateKey) (*ProtocolMessageSpec, error) { - return p.BuildPublicMessage(myIdentityKey, nil) -} - // BuildDirectMessage returns a 1:1 chat message and optionally a negotiated topic given the user identity private key, the recipient's public key, and a payload func (p *Protocol) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessageSpec, error) { logger := p.logger.With( @@ -253,18 +234,12 @@ func (p *Protocol) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey zap.Bool("has-shared-secret", sharedSecret != nil), zap.Bool("agreed", agreed)) - // Call handler - if sharedSecret != nil { - p.onNewSharedSecretHandler([]*sharedsecret.Secret{sharedSecret}) - } - spec := &ProtocolMessageSpec{ + SharedSecret: sharedSecret, + AgreedSecret: agreed, Message: message, Installations: installations, } - if agreed { - spec.SharedSecret = sharedSecret.Key - } return spec, nil } @@ -410,14 +385,21 @@ func (p *Protocol) ConfirmMessageProcessed(messageID []byte) error { return p.encryptor.ConfirmMessageProcessed(messageID) } +type DecryptMessageResponse struct { + DecryptedMessage []byte + Installations []*multidevice.Installation + SharedSecrets []*sharedsecret.Secret +} + // HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message. func (p *Protocol) HandleMessage( myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, protocolMessage *ProtocolMessage, messageID []byte, -) ([]byte, error) { +) (*DecryptMessageResponse, error) { logger := p.logger.With(zap.String("site", "HandleMessage")) + response := &DecryptMessageResponse{} logger.Debug("received a protocol message", zap.Binary("sender-public-key", crypto.FromECDSAPub(theirPublicKey)), zap.Binary("message-id", messageID)) @@ -428,19 +410,19 @@ func (p *Protocol) HandleMessage( // Process bundles for _, bundle := range protocolMessage.GetBundles() { // Should we stop processing if the bundle cannot be verified? - addedBundles, err := p.ProcessPublicBundle(myIdentityKey, bundle) + newInstallations, err := p.ProcessPublicBundle(myIdentityKey, bundle) if err != nil { return nil, err } - - p.onAddedBundlesHandler(addedBundles) + response.Installations = newInstallations } // Check if it's a public message if publicMessage := protocolMessage.GetPublicMessage(); publicMessage != nil { logger.Debug("received a public message in direct message") // Nothing to do, as already in cleartext - return publicMessage, nil + response.DecryptedMessage = publicMessage + return response, nil } // Decrypt message @@ -468,9 +450,10 @@ func (p *Protocol) HandleMessage( return nil, err } - p.onNewSharedSecretHandler([]*sharedsecret.Secret{sharedSecret}) + response.SharedSecrets = []*sharedsecret.Secret{sharedSecret} } - return message, nil + response.DecryptedMessage = message + return response, nil } // Return error diff --git a/protocol/encryption/protocol_test.go b/protocol/encryption/protocol_test.go index 7fba7301e..60e954af3 100644 --- a/protocol/encryption/protocol_test.go +++ b/protocol/encryption/protocol_test.go @@ -13,9 +13,6 @@ import ( "go.uber.org/zap" "github.com/status-im/status-go/eth-node/crypto" - - "github.com/status-im/status-go/protocol/encryption/multidevice" - "github.com/status-im/status-go/protocol/encryption/sharedsecret" ) func TestProtocolServiceTestSuite(t *testing.T) { @@ -44,17 +41,11 @@ func (s *ProtocolServiceTestSuite) SetupTest() { s.Require().NoError(err) bobDBKey := "bob" - addedBundlesHandler := func(addedBundles []*multidevice.Installation) {} - onNewSharedSecretHandler := func(secret []*sharedsecret.Secret) {} - db, err := sqlite.Open(s.aliceDBPath.Name(), aliceDBKey) s.Require().NoError(err) s.alice = New( db, "1", - addedBundlesHandler, - onNewSharedSecretHandler, - func(*ProtocolMessageSpec) {}, s.logger.With(zap.String("user", "alice")), ) @@ -63,9 +54,6 @@ func (s *ProtocolServiceTestSuite) SetupTest() { s.bob = New( db, "2", - addedBundlesHandler, - onNewSharedSecretHandler, - func(*ProtocolMessageSpec) {}, s.logger.With(zap.String("user", "bob")), ) } @@ -142,7 +130,6 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() { } func (s *ProtocolServiceTestSuite) TestSecretNegotiation() { - var secretResponse []*sharedsecret.Secret bobKey, err := crypto.GenerateKey() s.NoError(err) aliceKey, err := crypto.GenerateKey() @@ -150,12 +137,13 @@ func (s *ProtocolServiceTestSuite) TestSecretNegotiation() { payload := []byte("test") - s.bob.onNewSharedSecretHandler = func(secret []*sharedsecret.Secret) { - secretResponse = secret - } + _, err = s.bob.Start(bobKey) + s.Require().NoError(err) + msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload) s.NoError(err) s.NotNil(msgSpec, "It creates a message spec") + s.Require().NotNil(msgSpec.SharedSecret) bundle := msgSpec.Message.GetBundles()[0] s.Require().NotNil(bundle) @@ -171,12 +159,10 @@ func (s *ProtocolServiceTestSuite) TestSecretNegotiation() { _, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, msgSpec.Message, []byte("message-id")) s.NoError(err) - s.Require().NotNil(secretResponse) + s.Require().NoError(s.bob.Stop()) } func (s *ProtocolServiceTestSuite) TestPropagatingSavedSharedSecretsOnStart() { - var secretResponse []*sharedsecret.Secret - aliceKey, err := crypto.GenerateKey() s.NoError(err) bobKey, err := crypto.GenerateKey() @@ -186,15 +172,14 @@ func (s *ProtocolServiceTestSuite) TestPropagatingSavedSharedSecretsOnStart() { generatedSecret, err := s.alice.secret.Generate(aliceKey, &bobKey.PublicKey, "installation-1") s.NoError(err) - s.alice.onNewSharedSecretHandler = func(secret []*sharedsecret.Secret) { - secretResponse = secret - } + subscriptions, err := s.alice.Start(aliceKey) + s.Require().NoError(err) - err = s.alice.Start(aliceKey) - s.NoError(err) + secretResponse := subscriptions.SharedSecrets s.Require().NotNil(secretResponse) s.Require().Len(secretResponse, 1) s.Equal(crypto.FromECDSAPub(generatedSecret.Identity), crypto.FromECDSAPub(secretResponse[0].Identity)) s.Equal(generatedSecret.Key, secretResponse[0].Key) + s.Require().NoError(s.alice.Stop()) } diff --git a/protocol/encryption/publisher/publisher.go b/protocol/encryption/publisher/publisher.go index af42e38c6..28ea2af25 100644 --- a/protocol/encryption/publisher/publisher.go +++ b/protocol/encryption/publisher/publisher.go @@ -46,7 +46,7 @@ func (p *Publisher) Start() <-chan struct{} { logger.Info("starting publisher") - p.notifyCh = make(chan struct{}) + p.notifyCh = make(chan struct{}, 100) p.quit = make(chan struct{}) go p.tickerLoop() @@ -55,6 +55,10 @@ func (p *Publisher) Start() <-chan struct{} { } func (p *Publisher) Stop() { + // If hasn't started, ignore + if p.quit == nil { + return + } select { case _, ok := <-p.quit: if !ok { @@ -101,7 +105,11 @@ func (p *Publisher) notify() error { return errNotEnoughTimePassed } - p.notifyCh <- struct{}{} + select { + case p.notifyCh <- struct{}{}: + default: + p.logger.Warn("publisher channel full, dropping message") + } p.persistence.setLastPublished(now) return nil diff --git a/protocol/messenger.go b/protocol/messenger.go index ad4cdc137..013e32cc7 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -56,6 +56,7 @@ var ( // mailservers because they can also be managed by the user. type Messenger struct { node types.Node + config *config identity *ecdsa.PrivateKey persistence *sqlitePersistence transport transport.Transport @@ -127,50 +128,6 @@ func NewMessenger( } } - onNewInstallationsHandler := func(installations []*multidevice.Installation) { - - for _, installation := range installations { - if installation.Identity == contactIDFromPublicKey(&messenger.identity.PublicKey) { - if _, ok := messenger.allInstallations[installation.ID]; !ok { - messenger.allInstallations[installation.ID] = installation - messenger.modifiedInstallations[installation.ID] = true - } - } - } - } - // Set default config fields. - onNewSharedSecretHandler := func(secrets []*sharedsecret.Secret) { - filters, err := messenger.handleSharedSecrets(secrets) - if err != nil { - slogger := logger.With(zap.String("site", "onNewSharedSecretHandler")) - slogger.Warn("failed to process secrets", zap.Error(err)) - } - - if c.onNegotiatedFilters != nil { - c.onNegotiatedFilters(filters) - } - } - if c.onSendContactCodeHandler == nil { - c.onSendContactCodeHandler = func(messageSpec *encryption.ProtocolMessageSpec) { - slogger := logger.With(zap.String("site", "onSendContactCodeHandler")) - slogger.Debug("received a SendContactCode request") - - newMessage, err := common.MessageSpecToWhisper(messageSpec) - if err != nil { - slogger.Warn("failed to convert spec to Whisper message", zap.Error(err)) - return - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - chatName := transport.ContactCodeTopic(&messenger.identity.PublicKey) - _, err = messenger.transport.SendPublic(ctx, newMessage, chatName) - if err != nil { - slogger.Warn("failed to send a contact code", zap.Error(err)) - } - } - } - if c.systemMessagesTranslations == nil { c.systemMessagesTranslations = defaultSystemMessagesTranslations } @@ -232,9 +189,6 @@ func NewMessenger( encryptionProtocol := encryption.New( database, installationID, - onNewInstallationsHandler, - onNewSharedSecretHandler, - c.onSendContactCodeHandler, logger, ) @@ -267,8 +221,6 @@ func NewMessenger( // Overriding until we handle different identities pushNotificationClientConfig.Identity = identity - // Hardcoding this for now, as it's the only one we support - pushNotificationClientConfig.TokenType = protobuf.PushNotificationRegistration_APN_TOKEN pushNotificationClientConfig.Logger = logger pushNotificationClientConfig.InstallationID = installationID @@ -277,6 +229,7 @@ func NewMessenger( handler := newMessageHandler(identity, logger, &sqlitePersistence{db: database}) messenger = &Messenger{ + config: &c, node: node, identity: identity, persistence: &sqlitePersistence{db: database}, @@ -299,6 +252,7 @@ func NewMessenger( shutdownTasks: []func() error{ database.Close, pushNotificationClient.Stop, + encryptionProtocol.Stop, transp.ResetFilters, transp.Stop, func() error { processor.Stop(); return nil }, @@ -325,12 +279,147 @@ func (m *Messenger) Start() error { // Start push notification client if m.pushNotificationClient != nil { + m.handlePushNotificationClientRegistrations(m.pushNotificationClient.SubscribeToRegistrations()) + if err := m.pushNotificationClient.Start(); err != nil { return err } } - return m.encryptor.Start(m.identity) + // set shared secret handles + m.processor.SetHandleSharedSecrets(m.handleSharedSecrets) + + subscriptions, err := m.encryptor.Start(m.identity) + if err != nil { + return err + } + + // handle stored shared secrets + err = m.handleSharedSecrets(subscriptions.SharedSecrets) + if err != nil { + return err + } + + m.handleEncryptionLayerSubscriptions(subscriptions) + return nil +} + +func (m *Messenger) buildContactCodeAdvertisement() (*protobuf.ContactCodeAdvertisement, error) { + if m.pushNotificationClient == nil || !m.pushNotificationClient.Enabled() { + return nil, nil + } + m.logger.Debug("adding push notification info to contact code bundle") + info, err := m.pushNotificationClient.MyPushNotificationQueryInfo() + if err != nil { + return nil, err + } + if len(info) == 0 { + return nil, nil + } + return &protobuf.ContactCodeAdvertisement{ + PushNotificationInfo: info, + }, nil +} + +// handleSendContactCode sends a public message wrapped in the encryption +// layer, which will propagate our bundle +func (m *Messenger) handleSendContactCode() error { + var payload []byte + m.logger.Debug("sending contact code") + contactCodeAdvertisement, err := m.buildContactCodeAdvertisement() + if err != nil { + m.logger.Error("could not build contact code advertisement", zap.Error(err)) + } + + if contactCodeAdvertisement != nil { + payload, err = proto.Marshal(contactCodeAdvertisement) + if err != nil { + return err + } + } + + contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey) + rawMessage := common.RawMessage{ + LocalChatID: contactCodeTopic, + MessageType: protobuf.ApplicationMetadataMessage_CONTACT_CODE_ADVERTISEMENT, + Payload: payload, + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _, err = m.processor.SendPublic(ctx, contactCodeTopic, rawMessage) + if err != nil { + m.logger.Warn("failed to send a contact code", zap.Error(err)) + } + return err +} + +// handleSharedSecrets process the negotiated secrets received from the encryption layer +func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error { + logger := m.logger.With(zap.String("site", "handleSharedSecrets")) + var result []*transport.Filter + for _, secret := range secrets { + logger.Debug("received shared secret", zap.Binary("identity", crypto.FromECDSAPub(secret.Identity))) + fSecret := types.NegotiatedSecret{ + PublicKey: secret.Identity, + Key: secret.Key, + } + filter, err := m.transport.ProcessNegotiatedSecret(fSecret) + if err != nil { + return err + } + result = append(result, filter) + } + if m.config.onNegotiatedFilters != nil { + m.config.onNegotiatedFilters(result) + } + + return nil +} + +// handleInstallations adds the installations in the installations map +func (m *Messenger) handleInstallations(installations []*multidevice.Installation) { + for _, installation := range installations { + if installation.Identity == contactIDFromPublicKey(&m.identity.PublicKey) { + if _, ok := m.allInstallations[installation.ID]; !ok { + m.allInstallations[installation.ID] = installation + m.modifiedInstallations[installation.ID] = true + } + } + } +} + +// handleEncryptionLayerSubscriptions handles events from the encryption layer +func (m *Messenger) handleEncryptionLayerSubscriptions(subscriptions *encryption.Subscriptions) { + go func() { + for { + select { + case <-subscriptions.SendContactCode: + if err := m.handleSendContactCode(); err != nil { + m.logger.Error("failed to publish contact code", zap.Error(err)) + } + + case <-subscriptions.Quit: + m.logger.Debug("quitting encryption subscription loop") + return + } + } + }() +} + +// handlePushNotificationClientRegistration handles registration events +func (m *Messenger) handlePushNotificationClientRegistrations(c chan struct{}) { + go func() { + for { + _, more := <-c + if !more { + return + } + if err := m.handleSendContactCode(); err != nil { + m.logger.Error("failed to publish contact code", zap.Error(err)) + } + + } + }() } // Init analyzes chats and contacts in order to setup filters @@ -436,24 +525,6 @@ func (m *Messenger) Shutdown() (err error) { return } -func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) ([]*transport.Filter, error) { - logger := m.logger.With(zap.String("site", "handleSharedSecrets")) - var result []*transport.Filter - for _, secret := range secrets { - logger.Debug("received shared secret", zap.Binary("identity", crypto.FromECDSAPub(secret.Identity))) - fSecret := types.NegotiatedSecret{ - PublicKey: secret.Identity, - Key: secret.Key, - } - filter, err := m.transport.ProcessNegotiatedSecret(fSecret) - if err != nil { - return nil, err - } - result = append(result, filter) - } - return result, nil -} - func (m *Messenger) EnableInstallation(id string) error { m.mutex.Lock() defer m.mutex.Unlock() @@ -1152,7 +1223,7 @@ func (m *Messenger) saveContact(contact *Contact) error { } // We check if it should re-register with the push notification server - shouldReregisterForPushNotifications := m.pushNotificationClient != nil && (m.isNewContact(contact) || m.removedContact(contact)) + shouldReregisterForPushNotifications := (m.isNewContact(contact) || m.removedContact(contact)) err = m.persistence.SaveContact(contact, nil) if err != nil { @@ -1163,17 +1234,22 @@ func (m *Messenger) saveContact(contact *Contact) error { // Reregister only when data has changed if shouldReregisterForPushNotifications { - m.logger.Info("contact state changed, re-registering for push notification") - contactIDs, mutedChatIDs := m.addedContactsAndMutedChatIDs() - err := m.pushNotificationClient.Reregister(contactIDs, mutedChatIDs) - if err != nil { - return err - } + return m.reregisterForPushNotifications() } return nil } +func (m *Messenger) reregisterForPushNotifications() error { + m.logger.Info("contact state changed, re-registering for push notification") + if m.pushNotificationClient == nil { + return nil + } + + contactIDs, mutedChatIDs := m.addedContactsAndMutedChatIDs() + return m.pushNotificationClient.Reregister(contactIDs, mutedChatIDs) +} + func (m *Messenger) SaveContact(contact *Contact) error { m.mutex.Lock() defer m.mutex.Unlock() @@ -1816,6 +1892,13 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte for _, msg := range statusMessages { publicKey := msg.SigPubKey() + m.handleInstallations(msg.Installations) + err := m.handleSharedSecrets(msg.SharedSecrets) + if err != nil { + // log and continue, non-critical error + logger.Warn("failed to handle shared secrets") + } + // Check for messages from blocked users senderID := contactIDFromPublicKey(publicKey) if _, ok := messageState.AllContacts[senderID]; ok && messageState.AllContacts[senderID].IsBlocked() { @@ -1828,6 +1911,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte logger.Warn("failed to check message exists", zap.Error(err)) } if exists { + logger.Debug("messageExists", zap.String("messageID", messageID)) continue } @@ -1999,6 +2083,18 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte } // We continue in any case, no changes to messenger continue + case protobuf.ContactCodeAdvertisement: + logger.Debug("Received ContactCodeAdvertisement") + if m.pushNotificationClient == nil { + continue + } + logger.Debug("Handling ContactCodeAdvertisement") + if err := m.pushNotificationClient.HandleContactCodeAdvertisement(publicKey, msg.ParsedMessage.Interface().(protobuf.ContactCodeAdvertisement)); err != nil { + logger.Warn("failed to handle ContactCodeAdvertisement", zap.Error(err)) + } + // We continue in any case, no changes to messenger + continue + case protobuf.PushNotificationResponse: logger.Debug("Received PushNotificationResponse") if m.pushNotificationClient == nil { @@ -2060,6 +2156,8 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte logger.Debug("message not handled", zap.Any("messageType", reflect.TypeOf(msg.ParsedMessage.Interface()))) } + } else { + logger.Debug("parsed message is nil") } } } @@ -2251,7 +2349,8 @@ func (m *Messenger) MuteChat(chatID string) error { chat.Muted = true m.allChats[chat.ID] = chat - return nil + + return m.reregisterForPushNotifications() } // UnmuteChat signals to the messenger that we want to be notified @@ -2271,7 +2370,7 @@ func (m *Messenger) UnmuteChat(chatID string) error { chat.Muted = false m.allChats[chat.ID] = chat - return nil + return m.reregisterForPushNotifications() } func (m *Messenger) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error { @@ -3146,7 +3245,7 @@ func (m *Messenger) addedContactsAndMutedChatIDs() ([]*ecdsa.PublicKey, []string } // RegisterForPushNotification register deviceToken with any push notification server enabled -func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToken string) error { +func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToken, apnTopic string, tokenType protobuf.PushNotificationRegistration_TokenType) error { if m.pushNotificationClient == nil { return errors.New("push notification client not enabled") } @@ -3154,7 +3253,12 @@ func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToke defer m.mutex.Unlock() contactIDs, mutedChatIDs := m.addedContactsAndMutedChatIDs() - return m.pushNotificationClient.Register(deviceToken, contactIDs, mutedChatIDs) + err := m.pushNotificationClient.Register(deviceToken, apnTopic, tokenType, contactIDs, mutedChatIDs) + if err != nil { + m.logger.Error("failed to register for push notifications", zap.Error(err)) + return err + } + return nil } // RegisteredForPushNotifications returns whether we successfully registered with all the servers diff --git a/protocol/messenger_config.go b/protocol/messenger_config.go index b83f2c173..9d4841731 100644 --- a/protocol/messenger_config.go +++ b/protocol/messenger_config.go @@ -6,7 +6,6 @@ import ( "go.uber.org/zap" "github.com/status-im/status-go/protocol/common" - "github.com/status-im/status-go/protocol/encryption" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/pushnotificationclient" "github.com/status-im/status-go/protocol/pushnotificationserver" @@ -18,8 +17,6 @@ type config struct { // as otherwise the client is not notified of a new filter and // won't be pulling messages from mailservers until it reloads the chats/filters onNegotiatedFilters func([]*transport.Filter) - // DEPRECATED: no need to expose it - onSendContactCodeHandler func(*encryption.ProtocolMessageSpec) // systemMessagesTranslations holds translations for system-messages systemMessagesTranslations map[protobuf.MembershipUpdateEvent_EventType]string diff --git a/protocol/messenger_contact_update_test.go b/protocol/messenger_contact_update_test.go index 25a6e9de7..c89a7f645 100644 --- a/protocol/messenger_contact_update_test.go +++ b/protocol/messenger_contact_update_test.go @@ -44,6 +44,11 @@ func (s *MessengerContactUpdateSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + s.Require().NoError(s.m.Start()) +} + +func (s *MessengerContactUpdateSuite) TearDownTest() { + s.Require().NoError(s.m.Shutdown()) } func (s *MessengerContactUpdateSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { @@ -85,6 +90,7 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() { contactID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey)) theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirContactID := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) response, err := theirMessenger.SendContactUpdate(context.Background(), contactID, theirName, theirPicture) @@ -136,4 +142,5 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() { s.Require().False(receivedContact.ENSVerified) s.Require().True(receivedContact.HasBeenAdded()) s.Require().NotEmpty(receivedContact.LastUpdated) + s.Require().NoError(theirMessenger.Shutdown()) } diff --git a/protocol/messenger_emoji_test.go b/protocol/messenger_emoji_test.go index 297c62127..10c8cd266 100644 --- a/protocol/messenger_emoji_test.go +++ b/protocol/messenger_emoji_test.go @@ -47,6 +47,11 @@ func (s *MessengerEmojiSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + s.Require().NoError(s.m.Start()) +} + +func (s *MessengerEmojiSuite) TearDownTest() { + s.Require().NoError(s.m.Shutdown()) } func (s *MessengerEmojiSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { @@ -89,6 +94,7 @@ func (s *MessengerEmojiSuite) TestSendEmoji() { s.Require().NoError(err) bob := s.newMessengerWithKey(s.shh, key) + s.Require().NoError(bob.Start()) chatID := statusChatID @@ -162,11 +168,13 @@ func (s *MessengerEmojiSuite) TestSendEmoji() { s.Require().Equal(response.EmojiReactions[0].ID(), emojiID) s.Require().Equal(response.EmojiReactions[0].Type, protobuf.EmojiReaction_SAD) s.Require().True(response.EmojiReactions[0].Retracted) + s.Require().NoError(bob.Shutdown()) } func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() { bob := s.m alice := s.newMessenger(s.shh) + s.Require().NoError(alice.Start()) response, err := bob.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) @@ -217,4 +225,5 @@ func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() { "no emoji reaction received", ) s.Require().NoError(err) + s.Require().NoError(alice.Shutdown()) } diff --git a/protocol/messenger_installations_test.go b/protocol/messenger_installations_test.go index 41cf7e948..56e26c09a 100644 --- a/protocol/messenger_installations_test.go +++ b/protocol/messenger_installations_test.go @@ -50,6 +50,12 @@ func (s *MessengerInstallationSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + // We start the messenger in order to receive installations + s.Require().NoError(s.m.Start()) +} + +func (s *MessengerInstallationSuite) TearDownTest() { + s.Require().NoError(s.m.Shutdown()) } func (s *MessengerInstallationSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { @@ -88,6 +94,7 @@ func (s *MessengerInstallationSuite) newMessenger(shh types.Waku) *Messenger { func (s *MessengerInstallationSuite) TestReceiveInstallation() { theirMessenger := s.newMessengerWithKey(s.shh, s.privateKey) + s.Require().NoError(theirMessenger.Start()) err := theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{ Name: "their-name", @@ -153,6 +160,7 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() { actualChat := response.Chats[0] s.Require().Equal(statusChatID, actualChat.ID) s.Require().True(actualChat.Active) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerInstallationSuite) TestSyncInstallation() { @@ -174,6 +182,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() { // pair theirMessenger := s.newMessengerWithKey(s.shh, s.privateKey) + s.Require().NoError(theirMessenger.Start()) err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{ Name: "their-name", @@ -240,6 +249,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() { s.Require().NotNil(statusChat) s.Require().True(actualContact.IsAdded()) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { @@ -247,7 +257,9 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { bob1 := s.m // pair bob2 := s.newMessengerWithKey(s.shh, s.privateKey) + s.Require().NoError(bob2.Start()) alice := s.newMessenger(s.shh) + s.Require().NoError(alice.Start()) err := bob2.SetInstallationMetadata(bob2.installationID, &multidevice.InstallationMetadata{ Name: "their-name", @@ -290,4 +302,6 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { "message not received", ) s.Require().NoError(err) + s.Require().NoError(bob2.Shutdown()) + s.Require().NoError(alice.Shutdown()) } diff --git a/protocol/messenger_mute_test.go b/protocol/messenger_mute_test.go index 8fd525771..3fb6635f3 100644 --- a/protocol/messenger_mute_test.go +++ b/protocol/messenger_mute_test.go @@ -45,6 +45,11 @@ func (s *MessengerMuteSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + s.Require().NoError(s.m.Start()) +} + +func (s *MessengerMuteSuite) TearDownTest() { + s.Require().NoError(s.m.Shutdown()) } func (s *MessengerMuteSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { @@ -86,6 +91,7 @@ func (s *MessengerMuteSuite) TestSetMute() { s.Require().NoError(err) theirMessenger := s.newMessengerWithKey(s.shh, key) + s.Require().NoError(theirMessenger.Start()) chatID := "status" @@ -107,4 +113,5 @@ func (s *MessengerMuteSuite) TestSetMute() { s.Require().NoError(s.m.UnmuteChat(chatID)) s.Require().False(s.m.Chats()[0].Muted) + s.Require().NoError(theirMessenger.Shutdown()) } diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index 102d078bf..a22e19397 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -102,6 +102,7 @@ func (s *MessengerSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + s.Require().NoError(s.m.Start()) } func (s *MessengerSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger { @@ -503,6 +504,7 @@ func (s *MessengerSuite) TestRetrieveOwnPublic() { // Retrieve their public message func (s *MessengerSuite) TestRetrieveTheirPublic() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) @@ -539,10 +541,12 @@ func (s *MessengerSuite) TestRetrieveTheirPublic() { s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestDeletedAtClockValue() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) @@ -568,10 +572,12 @@ func (s *MessengerSuite) TestDeletedAtClockValue() { response, err := s.m.RetrieveAll() s.Require().NoError(err) s.Require().Len(response.Messages, 0) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestRetrieveBlockedContact() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) @@ -610,6 +616,7 @@ func (s *MessengerSuite) TestRetrieveBlockedContact() { // Resend their public message, receive only once func (s *MessengerSuite) TestResendPublicMessage() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) @@ -663,6 +670,7 @@ func (s *MessengerSuite) TestResendPublicMessage() { // Test receiving a message on an existing private chat func (s *MessengerSuite) TestRetrieveTheirPrivateChatExisting() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirChat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) @@ -698,11 +706,13 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatExisting() { // It sets the last message s.Require().NotNil(actualChat.LastMessage) s.Require().True(actualChat.Active) + s.Require().NoError(theirMessenger.Shutdown()) } // Test receiving a message on an non-existing private chat func (s *MessengerSuite) TestRetrieveTheirPrivateChatNonExisting() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) chat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) err := theirMessenger.SaveChat(&chat) s.NoError(err) @@ -739,6 +749,7 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateChatNonExisting() { // Test receiving a message on an non-existing public chat func (s *MessengerSuite) TestRetrieveTheirPublicChatNonExisting() { theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) chat := CreatePublicChat("test-chat", s.m.transport) err := theirMessenger.SaveChat(&chat) s.NoError(err) @@ -756,12 +767,14 @@ func (s *MessengerSuite) TestRetrieveTheirPublicChatNonExisting() { s.Require().Equal(len(response.Messages), 0) s.Require().Equal(len(response.Chats), 0) + s.Require().NoError(theirMessenger.Shutdown()) } // Test receiving a message on an existing private group chat func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { var response *MessengerResponse theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "id", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) @@ -823,6 +836,7 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { func (s *MessengerSuite) TestChangeNameGroupChat() { var response *MessengerResponse theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "old-name", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) @@ -869,12 +883,14 @@ func (s *MessengerSuite) TestChangeNameGroupChat() { s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] s.Require().Equal(newName, actualChat.Name) + s.Require().NoError(theirMessenger.Shutdown()) } // Test being re-invited to a group chat func (s *MessengerSuite) TestReInvitedToGroupChat() { var response *MessengerResponse theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "old-name", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) @@ -928,6 +944,7 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() { s.Require().Len(response.Chats, 1) s.Require().True(response.Chats[0].Active) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestChatPersistencePublic() { @@ -1388,7 +1405,7 @@ func (s *MessengerSuite) TestContactPersistenceUpdate() { } func (s *MessengerSuite) TestSharedSecretHandler() { - _, err := s.m.handleSharedSecrets(nil) + err := s.m.handleSharedSecrets(nil) s.NoError(err) } @@ -1434,6 +1451,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) @@ -1516,6 +1534,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { s.Require().Equal(CommandStateRequestAddressForTransactionDeclined, receiverMessage.CommandParameters.CommandState) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(initialCommandID, receiverMessage.Replace) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestSendEthTransaction() { @@ -1523,6 +1542,7 @@ func (s *MessengerSuite) TestSendEthTransaction() { contract := testContract theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) receiverAddress := crypto.PubkeyToAddress(theirMessenger.identity.PublicKey) @@ -1617,6 +1637,7 @@ func (s *MessengerSuite) TestSendEthTransaction() { s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal("", receiverMessage.Replace) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestSendTokenTransaction() { @@ -1624,6 +1645,7 @@ func (s *MessengerSuite) TestSendTokenTransaction() { contract := testContract theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) receiverAddress := crypto.PubkeyToAddress(theirMessenger.identity.PublicKey) @@ -1718,12 +1740,14 @@ func (s *MessengerSuite) TestSendTokenTransaction() { s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal(senderMessage.Replace, senderMessage.Replace) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) myAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) @@ -1808,6 +1832,7 @@ func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal("some-address", receiverMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, receiverMessage.Replace) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestDeclineRequestTransaction() { @@ -1816,6 +1841,7 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { receiverAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) @@ -1895,6 +1921,7 @@ func (s *MessengerSuite) TestDeclineRequestTransaction() { s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(initialCommandID, receiverMessage.Replace) s.Require().Equal(CommandStateRequestTransactionDeclined, receiverMessage.CommandParameters.CommandState) + s.Require().NoError(theirMessenger.Shutdown()) } func (s *MessengerSuite) TestRequestTransaction() { @@ -1903,6 +1930,7 @@ func (s *MessengerSuite) TestRequestTransaction() { receiverAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) theirMessenger := s.newMessenger(s.shh) + s.Require().NoError(theirMessenger.Start()) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) @@ -2039,6 +2067,7 @@ func (s *MessengerSuite) TestRequestTransaction() { s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal(senderMessage.Replace, senderMessage.Replace) + s.Require().NoError(theirMessenger.Shutdown()) } type MockTransaction struct { diff --git a/protocol/protobuf/push_notifications.pb.go b/protocol/protobuf/push_notifications.pb.go index 02dd42b56..48a2f5cb5 100644 --- a/protocol/protobuf/push_notifications.pb.go +++ b/protocol/protobuf/push_notifications.pb.go @@ -82,6 +82,34 @@ func (PushNotificationRegistrationResponse_ErrorType) EnumDescriptor() ([]byte, return fileDescriptor_200acd86044eaa5d, []int{1, 0} } +type PushNotification_PushNotificationType int32 + +const ( + PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0 + PushNotification_MESSAGE PushNotification_PushNotificationType = 1 + PushNotification_MENTION PushNotification_PushNotificationType = 2 +) + +var PushNotification_PushNotificationType_name = map[int32]string{ + 0: "UNKNOWN_PUSH_NOTIFICATION_TYPE", + 1: "MESSAGE", + 2: "MENTION", +} + +var PushNotification_PushNotificationType_value = map[string]int32{ + "UNKNOWN_PUSH_NOTIFICATION_TYPE": 0, + "MESSAGE": 1, + "MENTION": 2, +} + +func (x PushNotification_PushNotificationType) String() string { + return proto.EnumName(PushNotification_PushNotificationType_name, int32(x)) +} + +func (PushNotification_PushNotificationType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_200acd86044eaa5d, []int{6, 0} +} + type PushNotificationReport_ErrorType int32 const ( @@ -125,6 +153,7 @@ type PushNotificationRegistration struct { Unregister bool `protobuf:"varint,9,opt,name=unregister,proto3" json:"unregister,omitempty"` Grant []byte `protobuf:"bytes,10,opt,name=grant,proto3" json:"grant,omitempty"` AllowFromContactsOnly bool `protobuf:"varint,11,opt,name=allow_from_contacts_only,json=allowFromContactsOnly,proto3" json:"allow_from_contacts_only,omitempty"` + ApnTopic string `protobuf:"bytes,12,opt,name=apn_topic,json=apnTopic,proto3" json:"apn_topic,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -232,6 +261,13 @@ func (m *PushNotificationRegistration) GetAllowFromContactsOnly() bool { return false } +func (m *PushNotificationRegistration) GetApnTopic() string { + if m != nil { + return m.ApnTopic + } + return "" +} + type PushNotificationRegistrationResponse struct { Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` Error PushNotificationRegistrationResponse_ErrorType `protobuf:"varint,2,opt,name=error,proto3,enum=protobuf.PushNotificationRegistrationResponse_ErrorType" json:"error,omitempty"` @@ -508,14 +544,15 @@ func (m *PushNotificationQueryResponse) GetSuccess() bool { } type PushNotification struct { - AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` - ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` - PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` - InstallationId string `protobuf:"bytes,4,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` - Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` + PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + InstallationId string `protobuf:"bytes,4,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` + Message []byte `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Type PushNotification_PushNotificationType `protobuf:"varint,6,opt,name=type,proto3,enum=protobuf.PushNotification_PushNotificationType" json:"type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *PushNotification) Reset() { *m = PushNotification{} } @@ -578,6 +615,13 @@ func (m *PushNotification) GetMessage() []byte { return nil } +func (m *PushNotification) GetType() PushNotification_PushNotificationType { + if m != nil { + return m.Type + } + return PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE +} + type PushNotificationRequest struct { Requests []*PushNotification `protobuf:"bytes,1,rep,name=requests,proto3" json:"requests,omitempty"` MessageId []byte `protobuf:"bytes,2,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` @@ -738,6 +782,7 @@ func (m *PushNotificationResponse) GetReports() []*PushNotificationReport { func init() { proto.RegisterEnum("protobuf.PushNotificationRegistration_TokenType", PushNotificationRegistration_TokenType_name, PushNotificationRegistration_TokenType_value) proto.RegisterEnum("protobuf.PushNotificationRegistrationResponse_ErrorType", PushNotificationRegistrationResponse_ErrorType_name, PushNotificationRegistrationResponse_ErrorType_value) + proto.RegisterEnum("protobuf.PushNotification_PushNotificationType", PushNotification_PushNotificationType_name, PushNotification_PushNotificationType_value) proto.RegisterEnum("protobuf.PushNotificationReport_ErrorType", PushNotificationReport_ErrorType_name, PushNotificationReport_ErrorType_value) proto.RegisterType((*PushNotificationRegistration)(nil), "protobuf.PushNotificationRegistration") proto.RegisterType((*PushNotificationRegistrationResponse)(nil), "protobuf.PushNotificationRegistrationResponse") @@ -754,60 +799,65 @@ func init() { func init() { proto.RegisterFile("push_notifications.proto", fileDescriptor_200acd86044eaa5d) } var fileDescriptor_200acd86044eaa5d = []byte{ - // 878 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x41, 0x6f, 0xeb, 0x44, - 0x17, 0xfd, 0x9c, 0xa4, 0x4d, 0x72, 0x93, 0x2f, 0x4d, 0x47, 0x6d, 0x9f, 0x79, 0xa2, 0x10, 0x0c, - 0x12, 0x51, 0x17, 0x11, 0x2a, 0x12, 0xef, 0x89, 0x15, 0xa1, 0x75, 0x8b, 0xd5, 0xc6, 0x0e, 0x13, - 0x97, 0xa7, 0x27, 0x21, 0x59, 0x8e, 0x3d, 0x69, 0xad, 0xba, 0x1e, 0x33, 0x33, 0x2e, 0xca, 0x8e, - 0x1f, 0xc0, 0x86, 0x2d, 0x1b, 0xfe, 0x02, 0xe2, 0x17, 0x22, 0x8f, 0xed, 0xe0, 0x36, 0x6e, 0x5a, - 0x24, 0x56, 0xf6, 0x9c, 0xb9, 0xf7, 0xce, 0xcc, 0x39, 0xf7, 0x5c, 0x50, 0xe3, 0x84, 0xdf, 0x38, - 0x11, 0x15, 0xc1, 0x22, 0xf0, 0x5c, 0x11, 0xd0, 0x88, 0x8f, 0x62, 0x46, 0x05, 0x45, 0x2d, 0xf9, - 0x99, 0x27, 0x0b, 0xed, 0x8f, 0x06, 0x7c, 0x38, 0x4d, 0xf8, 0x8d, 0x59, 0x8a, 0xc2, 0xe4, 0x3a, - 0xe0, 0x82, 0xc9, 0x7f, 0x64, 0x01, 0x08, 0x7a, 0x4b, 0x22, 0x47, 0x2c, 0x63, 0xa2, 0x2a, 0x03, - 0x65, 0xd8, 0x3b, 0xfe, 0x62, 0x54, 0xe4, 0x8f, 0x36, 0xe5, 0x8e, 0xec, 0x34, 0xd1, 0x5e, 0xc6, - 0x04, 0xb7, 0x45, 0xf1, 0x8b, 0x3e, 0x81, 0xae, 0x4f, 0xee, 0x03, 0x8f, 0x38, 0x12, 0x53, 0x6b, - 0x03, 0x65, 0xd8, 0xc6, 0x9d, 0x0c, 0x93, 0x19, 0xe8, 0x73, 0xd8, 0x09, 0x22, 0x2e, 0xdc, 0x30, - 0x94, 0x75, 0x9c, 0xc0, 0x57, 0xeb, 0x32, 0xaa, 0x57, 0x86, 0x0d, 0x3f, 0xad, 0xe5, 0x7a, 0x1e, - 0xe1, 0x3c, 0xaf, 0xd5, 0xc8, 0x6a, 0x65, 0x58, 0x56, 0x4b, 0x85, 0x26, 0x89, 0xdc, 0x79, 0x48, - 0x7c, 0x75, 0x6b, 0xa0, 0x0c, 0x5b, 0xb8, 0x58, 0xa6, 0x3b, 0xf7, 0x84, 0xf1, 0x80, 0x46, 0xea, - 0xf6, 0x40, 0x19, 0x36, 0x70, 0xb1, 0x44, 0x43, 0xe8, 0xbb, 0x61, 0x48, 0x7f, 0x26, 0xbe, 0x73, - 0x4b, 0x96, 0x4e, 0x18, 0x70, 0xa1, 0x36, 0x07, 0xf5, 0x61, 0x17, 0xf7, 0x72, 0xfc, 0x82, 0x2c, - 0x2f, 0x03, 0x2e, 0xd0, 0x11, 0xec, 0xce, 0x43, 0xea, 0xdd, 0x12, 0xdf, 0xf1, 0x6e, 0x5c, 0x91, - 0x85, 0xb6, 0x64, 0xe8, 0x4e, 0xbe, 0x71, 0x72, 0xe3, 0x0a, 0x19, 0xfb, 0x11, 0x40, 0x12, 0x31, - 0xc9, 0x0f, 0x61, 0x6a, 0x5b, 0x5e, 0xa6, 0x84, 0xa0, 0x3d, 0xd8, 0xba, 0x66, 0x6e, 0x24, 0x54, - 0x18, 0x28, 0xc3, 0x2e, 0xce, 0x16, 0xe8, 0x0d, 0xa8, 0xf2, 0x4c, 0x67, 0xc1, 0xe8, 0x9d, 0xe3, - 0xd1, 0x48, 0xb8, 0x9e, 0xe0, 0x0e, 0x8d, 0xc2, 0xa5, 0xda, 0x91, 0x35, 0xf6, 0xe5, 0xfe, 0x19, - 0xa3, 0x77, 0x27, 0xf9, 0xae, 0x15, 0x85, 0x4b, 0xed, 0x0c, 0xda, 0x2b, 0xfe, 0xd1, 0x01, 0xa0, - 0x2b, 0xf3, 0xc2, 0xb4, 0xde, 0x99, 0x8e, 0x6d, 0x5d, 0xe8, 0xa6, 0x63, 0xbf, 0x9f, 0xea, 0xfd, - 0xff, 0xa1, 0xff, 0x43, 0x7b, 0x3c, 0xcd, 0xb1, 0xbe, 0x82, 0x10, 0xf4, 0xce, 0x0c, 0xac, 0x7f, - 0x3b, 0x9e, 0xe9, 0x39, 0x56, 0xd3, 0xfe, 0xaa, 0xc1, 0x67, 0x9b, 0x54, 0xc6, 0x84, 0xc7, 0x34, - 0xe2, 0x24, 0xe5, 0x93, 0x27, 0x92, 0x79, 0xd9, 0x26, 0x2d, 0x5c, 0x2c, 0x91, 0x09, 0x5b, 0x84, - 0x31, 0xca, 0xa4, 0xd6, 0xbd, 0xe3, 0xb7, 0x2f, 0x6b, 0x9f, 0xa2, 0xf0, 0x48, 0x4f, 0x73, 0x65, - 0x1b, 0x65, 0x65, 0xd0, 0x21, 0x00, 0x23, 0x3f, 0x25, 0x84, 0x8b, 0xa2, 0x35, 0xba, 0xb8, 0x9d, - 0x23, 0x86, 0xaf, 0xfd, 0xa2, 0x40, 0x7b, 0x95, 0x53, 0x7e, 0xba, 0x8e, 0xb1, 0x85, 0x8b, 0xa7, - 0xef, 0xc3, 0xee, 0x64, 0x7c, 0x79, 0x66, 0xe1, 0x89, 0x7e, 0xea, 0x4c, 0xf4, 0xd9, 0x6c, 0x7c, - 0xae, 0xf7, 0x15, 0xb4, 0x07, 0xfd, 0x1f, 0x74, 0x3c, 0x33, 0x2c, 0xd3, 0x99, 0x18, 0xb3, 0xc9, - 0xd8, 0x3e, 0xf9, 0xae, 0x5f, 0x43, 0xaf, 0xe1, 0xe0, 0xca, 0x9c, 0x5d, 0x4d, 0xa7, 0x16, 0xb6, - 0xf5, 0xd3, 0x32, 0x87, 0xf5, 0x94, 0x34, 0xc3, 0xb4, 0x75, 0x6c, 0x8e, 0x2f, 0xb3, 0x13, 0xfa, - 0x0d, 0x2d, 0x01, 0x35, 0x17, 0xe3, 0x84, 0xfa, 0x64, 0xec, 0xdf, 0x13, 0x26, 0x02, 0x4e, 0xee, - 0x48, 0x24, 0xd0, 0x7b, 0x38, 0x58, 0x33, 0xa6, 0x13, 0x44, 0x0b, 0xaa, 0x2a, 0x83, 0xfa, 0xb0, - 0x73, 0xfc, 0xe9, 0xd3, 0xf4, 0x7c, 0x9f, 0x10, 0xb6, 0x34, 0xa2, 0x05, 0xc5, 0x7b, 0xf1, 0xa3, - 0xad, 0x14, 0xd5, 0xde, 0xc2, 0x7e, 0x65, 0x0a, 0xfa, 0x18, 0x3a, 0x71, 0x32, 0x0f, 0x03, 0x2f, - 0x6d, 0x68, 0x2e, 0x0f, 0xea, 0x62, 0xc8, 0xa0, 0x0b, 0xb2, 0xe4, 0xda, 0xaf, 0x35, 0xf8, 0xe0, - 0xc9, 0xd3, 0xd6, 0x7c, 0xa6, 0xac, 0xfb, 0xac, 0xc2, 0xb3, 0xb5, 0x4a, 0xcf, 0x1e, 0x02, 0xfc, - 0x73, 0x95, 0x42, 0xbc, 0xd5, 0x4d, 0x2a, 0xbd, 0xd7, 0xa8, 0xf4, 0xde, 0xca, 0x2f, 0x5b, 0x65, - 0xbf, 0x3c, 0xed, 0xea, 0x23, 0xd8, 0xe5, 0x84, 0xdd, 0x13, 0xe6, 0x94, 0xce, 0x6f, 0xca, 0xdc, - 0x9d, 0x6c, 0x63, 0x5a, 0xdc, 0x42, 0xfb, 0x4d, 0x81, 0xc3, 0x4a, 0x3a, 0x56, 0xdd, 0xfe, 0x06, - 0x1a, 0xff, 0x56, 0x33, 0x99, 0x90, 0xbe, 0xff, 0x8e, 0x70, 0xee, 0x5e, 0x93, 0x82, 0xa3, 0x2e, - 0x6e, 0xe7, 0x88, 0xe1, 0x97, 0x5d, 0x54, 0x7f, 0xe0, 0x22, 0xed, 0x4f, 0x05, 0xfa, 0x8f, 0x8b, - 0xbf, 0x44, 0x99, 0x57, 0xd0, 0x94, 0xb3, 0x69, 0xa5, 0xc8, 0x76, 0xba, 0x7c, 0x5e, 0x89, 0x0a, - 0x45, 0x1b, 0x95, 0x8a, 0xaa, 0xd0, 0xcc, 0xef, 0x9f, 0x4b, 0x51, 0x2c, 0xb5, 0x18, 0x5e, 0xad, - 0x3b, 0x5c, 0xda, 0x14, 0x7d, 0x05, 0xad, 0xdc, 0xb1, 0x3c, 0xe7, 0xf0, 0xf5, 0x86, 0xb1, 0xb0, - 0x8a, 0x7d, 0x86, 0x3e, 0xed, 0xf7, 0x1a, 0x1c, 0xac, 0x1f, 0x19, 0x53, 0x26, 0x36, 0xcc, 0xa7, - 0x6f, 0x1e, 0xce, 0xa7, 0xa3, 0x4d, 0xf3, 0x29, 0x2d, 0x55, 0x39, 0x91, 0xfe, 0x0b, 0x2a, 0xb5, - 0x1f, 0x5f, 0x32, 0xb9, 0x76, 0xa0, 0xf3, 0x0e, 0x5b, 0xe6, 0x79, 0x79, 0x6c, 0x3f, 0x9a, 0x40, - 0xb5, 0x14, 0x33, 0x2d, 0xdb, 0xc1, 0xfa, 0xb9, 0x31, 0xb3, 0x75, 0xac, 0x9f, 0xf6, 0xeb, 0xe9, - 0x54, 0x5a, 0x7f, 0x50, 0xde, 0xcf, 0x0f, 0x79, 0x55, 0x1e, 0xb7, 0xe5, 0xd7, 0xd0, 0x64, 0xf2, - 0xed, 0x5c, 0xad, 0x49, 0xb5, 0x06, 0xcf, 0x91, 0x84, 0x8b, 0x84, 0xf9, 0xb6, 0x8c, 0xfc, 0xf2, - 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6f, 0xe8, 0xb9, 0x16, 0x90, 0x08, 0x00, 0x00, + // 952 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0xc6, 0x4e, 0xda, 0x24, 0x27, 0x21, 0x75, 0x47, 0x6d, 0xd7, 0x2c, 0x74, 0x09, 0x06, 0x89, + 0xa8, 0x17, 0x01, 0x15, 0x89, 0x5d, 0x71, 0x45, 0x48, 0x9d, 0xae, 0xd5, 0xc6, 0x0e, 0x13, 0x97, + 0xd5, 0x4a, 0x48, 0x96, 0x63, 0x4f, 0x5a, 0xab, 0xae, 0xc7, 0x78, 0x26, 0x45, 0xb9, 0xe3, 0x01, + 0xb8, 0xe1, 0x96, 0xc7, 0xe0, 0x9a, 0x37, 0xe0, 0xa5, 0x90, 0xc7, 0x76, 0x9a, 0x36, 0x6e, 0x5a, + 0x24, 0xae, 0x92, 0xf3, 0x9d, 0x9f, 0x99, 0x39, 0xdf, 0xf9, 0x8e, 0x41, 0x8d, 0xe7, 0xec, 0xca, + 0x89, 0x28, 0x0f, 0x66, 0x81, 0xe7, 0xf2, 0x80, 0x46, 0xac, 0x17, 0x27, 0x94, 0x53, 0x54, 0x17, + 0x3f, 0xd3, 0xf9, 0x4c, 0xfb, 0xbb, 0x0a, 0x9f, 0x8c, 0xe7, 0xec, 0xca, 0x5c, 0x89, 0xc2, 0xe4, + 0x32, 0x60, 0x3c, 0x11, 0xff, 0x91, 0x05, 0xc0, 0xe9, 0x35, 0x89, 0x1c, 0xbe, 0x88, 0x89, 0x2a, + 0x75, 0xa4, 0x6e, 0xfb, 0xf8, 0xeb, 0x5e, 0x91, 0xdf, 0xdb, 0x94, 0xdb, 0xb3, 0xd3, 0x44, 0x7b, + 0x11, 0x13, 0xdc, 0xe0, 0xc5, 0x5f, 0xf4, 0x19, 0xb4, 0x7c, 0x72, 0x1b, 0x78, 0xc4, 0x11, 0x98, + 0x2a, 0x77, 0xa4, 0x6e, 0x03, 0x37, 0x33, 0x4c, 0x64, 0xa0, 0x2f, 0x61, 0x27, 0x88, 0x18, 0x77, + 0xc3, 0x50, 0xd4, 0x71, 0x02, 0x5f, 0xad, 0x88, 0xa8, 0xf6, 0x2a, 0x6c, 0xf8, 0x69, 0x2d, 0xd7, + 0xf3, 0x08, 0x63, 0x79, 0xad, 0x6a, 0x56, 0x2b, 0xc3, 0xb2, 0x5a, 0x2a, 0xd4, 0x48, 0xe4, 0x4e, + 0x43, 0xe2, 0xab, 0x5b, 0x1d, 0xa9, 0x5b, 0xc7, 0x85, 0x99, 0x7a, 0x6e, 0x49, 0xc2, 0x02, 0x1a, + 0xa9, 0xdb, 0x1d, 0xa9, 0x5b, 0xc5, 0x85, 0x89, 0xba, 0xa0, 0xb8, 0x61, 0x48, 0x7f, 0x25, 0xbe, + 0x73, 0x4d, 0x16, 0x4e, 0x18, 0x30, 0xae, 0xd6, 0x3a, 0x95, 0x6e, 0x0b, 0xb7, 0x73, 0xfc, 0x8c, + 0x2c, 0xce, 0x03, 0xc6, 0xd1, 0x11, 0xec, 0x4e, 0x43, 0xea, 0x5d, 0x13, 0xdf, 0xf1, 0xae, 0x5c, + 0x9e, 0x85, 0xd6, 0x45, 0xe8, 0x4e, 0xee, 0x18, 0x5c, 0xb9, 0x5c, 0xc4, 0xbe, 0x02, 0x98, 0x47, + 0x89, 0xe8, 0x0f, 0x49, 0xd4, 0x86, 0xb8, 0xcc, 0x0a, 0x82, 0xf6, 0x60, 0xeb, 0x32, 0x71, 0x23, + 0xae, 0x42, 0x47, 0xea, 0xb6, 0x70, 0x66, 0xa0, 0xd7, 0xa0, 0x8a, 0x33, 0x9d, 0x59, 0x42, 0x6f, + 0x1c, 0x8f, 0x46, 0xdc, 0xf5, 0x38, 0x73, 0x68, 0x14, 0x2e, 0xd4, 0xa6, 0xa8, 0xb1, 0x2f, 0xfc, + 0xc3, 0x84, 0xde, 0x0c, 0x72, 0xaf, 0x15, 0x85, 0x0b, 0xf4, 0x31, 0x34, 0xdc, 0x38, 0x72, 0x38, + 0x8d, 0x03, 0x4f, 0x6d, 0x89, 0xc6, 0xd4, 0xdd, 0x38, 0xb2, 0x53, 0x5b, 0x1b, 0x42, 0x63, 0x49, + 0x0e, 0x3a, 0x00, 0x74, 0x61, 0x9e, 0x99, 0xd6, 0x3b, 0xd3, 0xb1, 0xad, 0x33, 0xdd, 0x74, 0xec, + 0xf7, 0x63, 0x5d, 0xf9, 0x00, 0x7d, 0x08, 0x8d, 0xfe, 0x38, 0xc7, 0x14, 0x09, 0x21, 0x68, 0x0f, + 0x0d, 0xac, 0xff, 0xd0, 0x9f, 0xe8, 0x39, 0x26, 0x6b, 0x7f, 0xc9, 0xf0, 0xc5, 0xa6, 0x11, 0xc0, + 0x84, 0xc5, 0x34, 0x62, 0x24, 0x6d, 0x36, 0x9b, 0x0b, 0x5a, 0xc4, 0x0c, 0xd5, 0x71, 0x61, 0x22, + 0x13, 0xb6, 0x48, 0x92, 0xd0, 0x44, 0x0c, 0x42, 0xfb, 0xf8, 0xcd, 0xf3, 0x66, 0xab, 0x28, 0xdc, + 0xd3, 0xd3, 0x5c, 0x31, 0x63, 0x59, 0x19, 0x74, 0x08, 0x90, 0x90, 0x5f, 0xe6, 0x84, 0xf1, 0x62, + 0x6e, 0x5a, 0xb8, 0x91, 0x23, 0x86, 0xaf, 0xfd, 0x26, 0x41, 0x63, 0x99, 0xb3, 0xfa, 0x74, 0x1d, + 0x63, 0x0b, 0x17, 0x4f, 0xdf, 0x87, 0xdd, 0x51, 0xff, 0x7c, 0x68, 0xe1, 0x91, 0x7e, 0xe2, 0x8c, + 0xf4, 0xc9, 0xa4, 0x7f, 0xaa, 0x2b, 0x12, 0xda, 0x03, 0xe5, 0x27, 0x1d, 0x4f, 0x0c, 0xcb, 0x74, + 0x46, 0xc6, 0x64, 0xd4, 0xb7, 0x07, 0x6f, 0x15, 0x19, 0xbd, 0x84, 0x83, 0x0b, 0x73, 0x72, 0x31, + 0x1e, 0x5b, 0xd8, 0xd6, 0x4f, 0x56, 0x7b, 0x58, 0x49, 0x9b, 0x66, 0x98, 0xb6, 0x8e, 0xcd, 0xfe, + 0x79, 0x76, 0x82, 0x52, 0xd5, 0xe6, 0xa0, 0xe6, 0x4c, 0x0d, 0xa8, 0x4f, 0xfa, 0xfe, 0x2d, 0x49, + 0x78, 0xc0, 0xc8, 0x0d, 0x89, 0x38, 0x7a, 0x0f, 0x07, 0x6b, 0xaa, 0x75, 0x82, 0x68, 0x46, 0x55, + 0xa9, 0x53, 0xe9, 0x36, 0x8f, 0x3f, 0x7f, 0xbc, 0x3d, 0x3f, 0xce, 0x49, 0xb2, 0x30, 0xa2, 0x19, + 0xc5, 0x7b, 0xf1, 0x03, 0x57, 0x8a, 0x6a, 0x6f, 0x60, 0xbf, 0x34, 0x05, 0x7d, 0x0a, 0xcd, 0x78, + 0x3e, 0x0d, 0x03, 0x2f, 0x9d, 0x76, 0x26, 0x0e, 0x6a, 0x61, 0xc8, 0xa0, 0x33, 0xb2, 0x60, 0xda, + 0xef, 0x32, 0x7c, 0xf4, 0xe8, 0x69, 0x6b, 0x22, 0x94, 0xd6, 0x45, 0x58, 0x22, 0x68, 0xb9, 0x54, + 0xd0, 0x87, 0x00, 0x77, 0x57, 0x29, 0xc8, 0x5b, 0xde, 0xa4, 0x54, 0x98, 0xd5, 0x52, 0x61, 0x2e, + 0xc5, 0xb4, 0xb5, 0x2a, 0xa6, 0xc7, 0x25, 0x7f, 0x04, 0xbb, 0x8c, 0x24, 0xb7, 0x24, 0x71, 0x56, + 0xce, 0xaf, 0x89, 0xdc, 0x9d, 0xcc, 0x31, 0x2e, 0x6e, 0xa1, 0xfd, 0x21, 0xc1, 0x61, 0x69, 0x3b, + 0x96, 0xd3, 0xfe, 0x1a, 0xaa, 0xff, 0x95, 0x33, 0x91, 0x90, 0xbe, 0xff, 0x86, 0x30, 0xe6, 0x5e, + 0x92, 0xa2, 0x47, 0x2d, 0xdc, 0xc8, 0x11, 0xc3, 0x5f, 0x55, 0x51, 0xe5, 0x9e, 0x8a, 0xb4, 0x7f, + 0x64, 0x50, 0x1e, 0x16, 0x7f, 0x0e, 0x33, 0x2f, 0xa0, 0x26, 0x16, 0xd7, 0x92, 0x91, 0xed, 0xd4, + 0x7c, 0x9a, 0x89, 0x12, 0x46, 0xab, 0xa5, 0x8c, 0xaa, 0x50, 0xcb, 0xef, 0x9f, 0x53, 0x51, 0x98, + 0x68, 0x00, 0x55, 0xf1, 0x4d, 0xd9, 0x16, 0xba, 0xff, 0xea, 0xf1, 0x26, 0xad, 0x01, 0x42, 0xee, + 0x22, 0x59, 0xb3, 0x61, 0xaf, 0xcc, 0x8b, 0x34, 0x78, 0x55, 0x08, 0x7b, 0x7c, 0x31, 0x79, 0xeb, + 0x98, 0x96, 0x6d, 0x0c, 0x8d, 0x41, 0xdf, 0x4e, 0xb5, 0x9b, 0x8b, 0xbc, 0x09, 0xb5, 0x3b, 0x69, + 0x0b, 0xc3, 0x4c, 0xdd, 0x8a, 0xac, 0xc5, 0xf0, 0x62, 0x7d, 0xf9, 0x88, 0x0d, 0x82, 0xbe, 0x85, + 0x7a, 0xbe, 0x4c, 0x58, 0x4e, 0xef, 0xcb, 0x0d, 0x1b, 0x6b, 0x19, 0xfb, 0x04, 0xb3, 0xda, 0x9f, + 0x32, 0x1c, 0xac, 0x1f, 0x19, 0xd3, 0x84, 0x6f, 0x58, 0x9d, 0xdf, 0xdf, 0x5f, 0x9d, 0x47, 0x9b, + 0x56, 0x67, 0x5a, 0xaa, 0x74, 0x59, 0xfe, 0x1f, 0x2c, 0x6b, 0x3f, 0x3f, 0x67, 0xa9, 0xee, 0x40, + 0xf3, 0x1d, 0xb6, 0xcc, 0xd3, 0xd5, 0x2f, 0xca, 0x83, 0xe5, 0x28, 0xa7, 0x98, 0x69, 0xd9, 0x0e, + 0xd6, 0x4f, 0x8d, 0x89, 0xad, 0x63, 0xfd, 0x44, 0xa9, 0xa4, 0x0b, 0x73, 0xfd, 0x41, 0xb9, 0xd4, + 0xee, 0xf7, 0x55, 0x7a, 0xa8, 0x98, 0xef, 0xa0, 0x96, 0x88, 0xb7, 0x33, 0x55, 0x16, 0x6c, 0x75, + 0x9e, 0x6a, 0x12, 0x2e, 0x12, 0xa6, 0xdb, 0x22, 0xf2, 0x9b, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, + 0xc6, 0x95, 0x5d, 0x48, 0x48, 0x09, 0x00, 0x00, } diff --git a/protocol/protobuf/push_notifications.proto b/protocol/protobuf/push_notifications.proto index ce4ec1c45..47802f0db 100644 --- a/protocol/protobuf/push_notifications.proto +++ b/protocol/protobuf/push_notifications.proto @@ -19,6 +19,7 @@ message PushNotificationRegistration { bool unregister = 9; bytes grant = 10; bool allow_from_contacts_only = 11; + string apn_topic = 12; } message PushNotificationRegistrationResponse { @@ -65,6 +66,12 @@ message PushNotification { bytes public_key = 3; string installation_id = 4; bytes message = 5; + PushNotificationType type = 6; + enum PushNotificationType { + UNKNOWN_PUSH_NOTIFICATION_TYPE = 0; + MESSAGE = 1; + MENTION = 2; + } } message PushNotificationRequest { diff --git a/protocol/push_notification_test.go b/protocol/push_notification_test.go index aeda40dbc..2aa92791a 100644 --- a/protocol/push_notification_test.go +++ b/protocol/push_notification_test.go @@ -17,6 +17,7 @@ import ( "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/pushnotificationclient" "github.com/status-im/status-go/protocol/pushnotificationserver" "github.com/status-im/status-go/protocol/tt" @@ -26,6 +27,7 @@ import ( const ( bob1DeviceToken = "token-1" bob2DeviceToken = "token-2" + testAPNTopic = "topic" ) func TestMessengerPushNotificationSuite(t *testing.T) { @@ -54,6 +56,7 @@ func (s *MessengerPushNotificationSuite) SetupTest() { s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity + s.Require().NoError(s.m.Start()) } func (s *MessengerPushNotificationSuite) TearDownTest() { @@ -124,6 +127,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { bob1 := s.m bob2 := s.newMessengerWithKey(s.shh, s.m.identity) + s.Require().NoError(bob2.Start()) serverKey, err := crypto.GenerateKey() s.Require().NoError(err) @@ -139,7 +143,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey) s.Require().NoError(err) - err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken) + err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { @@ -169,7 +173,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotification() { err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey) s.Require().NoError(err) - err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken) + err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) err = tt.RetryWithBackOff(func() error { @@ -319,7 +323,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationFromContactO err = bob.EnablePushNotificationsFromContactsOnly() s.Require().NoError(err) - err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken) + err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) // Pull servers and check we registered @@ -430,6 +434,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() { alice := s.newMessenger(s.shh) // another contact to invalidate the token frank := s.newMessenger(s.shh) + s.Require().NoError(frank.Start()) // start alice and enable push notifications s.Require().NoError(alice.Start()) s.Require().NoError(alice.EnableSendingPushNotifications()) @@ -463,7 +468,7 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() { err = bob.EnablePushNotificationsFromContactsOnly() s.Require().NoError(err) - err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken) + err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) // Pull servers and check we registered @@ -650,7 +655,7 @@ func (s *MessengerPushNotificationSuite) TestActAsYourOwnPushNotificationServer( err := bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey) s.Require().NoError(err) - err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken) + err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) // Pull servers and check we registered err = tt.RetryWithBackOff(func() error { @@ -680,7 +685,7 @@ func (s *MessengerPushNotificationSuite) TestActAsYourOwnPushNotificationServer( err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey) s.Require().NoError(err) - err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken) + err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) s.Require().NoError(err) err = tt.RetryWithBackOff(func() error { @@ -796,3 +801,54 @@ func (s *MessengerPushNotificationSuite) TestActAsYourOwnPushNotificationServer( s.Require().NoError(bob2.Shutdown()) s.Require().NoError(alice.Shutdown()) } + +func (s *MessengerPushNotificationSuite) TestContactCode() { + + bob1 := s.m + + serverKey, err := crypto.GenerateKey() + s.Require().NoError(err) + server := s.newPushNotificationServer(s.shh, serverKey) + + alice := s.newMessenger(s.shh) + // start alice and enable sending push notifications + s.Require().NoError(alice.Start()) + s.Require().NoError(alice.EnableSendingPushNotifications()) + + // Register bob1 + err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey) + s.Require().NoError(err) + + err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN) + + // Pull servers and check we registered + err = tt.RetryWithBackOff(func() error { + _, err = server.RetrieveAll() + if err != nil { + return err + } + _, err = bob1.RetrieveAll() + if err != nil { + return err + } + registered, err := bob1.RegisteredForPushNotifications() + if err != nil { + return err + } + if !registered { + return errors.New("not registered") + } + return nil + }) + // Make sure we receive it + s.Require().NoError(err) + + contactCodeAdvertisement, err := bob1.buildContactCodeAdvertisement() + s.Require().NoError(err) + s.Require().NotNil(contactCodeAdvertisement) + + s.Require().NoError(alice.pushNotificationClient.HandleContactCodeAdvertisement(&bob1.identity.PublicKey, *contactCodeAdvertisement)) + + s.Require().NoError(alice.Shutdown()) + s.Require().NoError(server.Shutdown()) +} diff --git a/protocol/pushnotificationclient/client.go b/protocol/pushnotificationclient/client.go index aeb7a8a12..cc5fc0e97 100644 --- a/protocol/pushnotificationclient/client.go +++ b/protocol/pushnotificationclient/client.go @@ -121,9 +121,6 @@ type Config struct { InstallationID string Logger *zap.Logger - - // TokenType is the type of token - TokenType protobuf.PushNotificationRegistration_TokenType } type Client struct { @@ -141,6 +138,10 @@ type Client struct { AccessToken string // deviceToken is the device token for this device deviceToken string + // TokenType is the type of token + tokenType protobuf.PushNotificationRegistration_TokenType + // APNTopic is the topic of the apn topic for push notification + apnTopic string // randomReader only used for testing so we have deterministic encryption reader io.Reader @@ -155,6 +156,9 @@ type Client struct { resendingLoopQuitChan chan struct{} quit chan struct{} + + // registrationSubscriptions is a list of chan of client subscribed to the registration event + registrationSubscriptions []chan struct{} } func New(persistence *Persistence, config *Config, processor *common.MessageProcessor) *Client { @@ -179,16 +183,38 @@ func (c *Client) Start() error { c.subscribeForSentMessages() c.subscribeForScheduledMessages() + + // We start even if push notifications are disabled, as we might + // actually be sending an unregister message c.startRegistrationLoop() + c.startResendingLoop() return nil } +func (c *Client) publishOnRegistrationSubscriptions() { + // Publish on channels, drop if buffer is full + for _, s := range c.registrationSubscriptions { + select { + case s <- struct{}{}: + default: + c.config.Logger.Warn("subscription channel full, dropping message") + } + } +} + +func (c *Client) quitRegistrationSubscriptions() { + for _, s := range c.registrationSubscriptions { + close(s) + } +} + func (c *Client) Stop() error { close(c.quit) c.stopRegistrationLoop() c.stopResendingLoop() + c.quitRegistrationSubscriptions() return nil } @@ -197,6 +223,8 @@ func (c *Client) Unregister() error { // stop registration loop c.stopRegistrationLoop() + c.config.RemoteNotificationsEnabled = false + registration := c.buildPushNotificationUnregisterMessage() err := c.saveLastPushNotificationRegistration(registration, nil) if err != nil { @@ -230,6 +258,12 @@ func (c *Client) Registered() (bool, error) { return true, nil } +func (c *Client) SubscribeToRegistrations() chan struct{} { + s := make(chan struct{}, 100) + c.registrationSubscriptions = append(c.registrationSubscriptions, s) + return s +} + func (c *Client) GetSentNotification(hashedPublicKey []byte, installationID string, messageID []byte) (*SentNotification, error) { return c.persistence.GetSentNotification(hashedPublicKey, installationID, messageID) } @@ -245,14 +279,21 @@ func (c *Client) Reregister(contactIDs []*ecdsa.PublicKey, mutedChatIDs []string return nil } - return c.Register(c.deviceToken, contactIDs, mutedChatIDs) + if !c.config.RemoteNotificationsEnabled { + c.config.Logger.Info("remote notifications not enabled, not registering") + return nil + } + + return c.Register(c.deviceToken, c.apnTopic, c.tokenType, contactIDs, mutedChatIDs) } // Register registers with all the servers -func (c *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error { +func (c *Client) Register(deviceToken, apnTopic string, tokenType protobuf.PushNotificationRegistration_TokenType, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error { // stop registration loop c.stopRegistrationLoop() + c.config.RemoteNotificationsEnabled = true + // reset servers err := c.resetServers() if err != nil { @@ -260,6 +301,8 @@ func (c *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mut } c.deviceToken = deviceToken + c.apnTopic = apnTopic + c.tokenType = tokenType registration, err := c.buildPushNotificationRegistrationMessage(contactIDs, mutedChatIDs) if err != nil { @@ -299,7 +342,60 @@ func (c *Client) HandlePushNotificationRegistrationResponse(publicKey *ecdsa.Pub server.Registered = true server.RegisteredAt = time.Now().Unix() - return c.persistence.UpsertServer(server) + err = c.persistence.UpsertServer(server) + if err != nil { + return err + } + c.publishOnRegistrationSubscriptions() + + return nil +} + +// processQueryInfo takes info about push notifications and validates them +func (c *Client) processQueryInfo(clientPublicKey *ecdsa.PublicKey, serverPublicKey *ecdsa.PublicKey, info *protobuf.PushNotificationQueryInfo) error { + // make sure the public key matches + if !bytes.Equal(info.PublicKey, common.HashPublicKey(clientPublicKey)) { + c.config.Logger.Warn("reply for different key, ignoring") + return errors.New("reply for a different key, ignoring") + } + + accessToken := info.AccessToken + + // the user wants notification from contacts only, try to decrypt the access token to see if we are in their contacts + if len(accessToken) == 0 && len(info.AllowedKeyList) != 0 { + accessToken = c.handleAllowedKeyList(clientPublicKey, info.AllowedKeyList) + + } + + // no luck + if len(accessToken) == 0 { + c.config.Logger.Debug("not in the allowed key list") + return nil + } + + // We check the user has allowed this server to store this particular + // access token, otherwise anyone could reply with a fake token + // and receive notifications for a user + if err := c.handleGrant(clientPublicKey, serverPublicKey, info.Grant, accessToken); err != nil { + c.config.Logger.Warn("grant verification failed, ignoring", zap.Error(err)) + return err + } + + pushNotificationInfo := &PushNotificationInfo{ + PublicKey: clientPublicKey, + ServerPublicKey: serverPublicKey, + AccessToken: accessToken, + InstallationID: info.InstallationId, + Version: info.Version, + RetrievedAt: time.Now().Unix(), + } + + err := c.persistence.SavePushNotificationInfo([]*PushNotificationInfo{pushNotificationInfo}) + if err != nil { + c.config.Logger.Error("failed to save push notifications", zap.Error(err)) + return err + } + return nil } // HandlePushNotificationQueryResponse should update the data in the database for a given user @@ -310,63 +406,55 @@ func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.Publ } // get the public key associated with this query - publicKey, err := c.persistence.GetQueryPublicKey(response.MessageId) + clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId) if err != nil { return err } - if publicKey == nil { + if clientPublicKey == nil { c.config.Logger.Debug("query not found") return nil } - var pushNotificationInfo []*PushNotificationInfo + // process query, make sure to validate grant as coming from the server for _, info := range response.Info { - // make sure the public key matches - if !bytes.Equal(info.PublicKey, common.HashPublicKey(publicKey)) { - c.config.Logger.Warn("reply for different key, ignoring") + err := c.processQueryInfo(clientPublicKey, serverPublicKey, info) + if err != nil { + + c.config.Logger.Warn("failed to process info", zap.Any("info", info), zap.Error(err)) continue } - - accessToken := info.AccessToken - - // the user wants notification from contacts only, try to decrypt the access token to see if we are in their contacts - if len(accessToken) == 0 && len(info.AllowedKeyList) != 0 { - accessToken = c.handleAllowedKeyList(publicKey, info.AllowedKeyList) - - } - - // no luck - if len(accessToken) == 0 { - c.config.Logger.Debug("not in the allowed key list") - continue - } - - // We check the user has allowed this server to store this particular - // access token, otherwise anyone could reply with a fake token - // and receive notifications for a user - if err := c.handleGrant(publicKey, serverPublicKey, info.Grant, accessToken); err != nil { - c.config.Logger.Warn("grant verification failed, ignoring", zap.Error(err)) - continue - } - - pushNotificationInfo = append(pushNotificationInfo, &PushNotificationInfo{ - PublicKey: publicKey, - ServerPublicKey: serverPublicKey, - AccessToken: accessToken, - InstallationID: info.InstallationId, - Version: info.Version, - RetrievedAt: time.Now().Unix(), - }) - } - - err = c.persistence.SavePushNotificationInfo(pushNotificationInfo) - if err != nil { - c.config.Logger.Error("failed to save push notifications", zap.Error(err)) - return err - } - return nil + +} + +// HandleContactCodeAdvertisement checks if there are any info and process them +func (c *Client) HandleContactCodeAdvertisement(clientPublicKey *ecdsa.PublicKey, message protobuf.ContactCodeAdvertisement) error { + // nothing to do for our own pubkey + if common.IsPubKeyEqual(clientPublicKey, &c.config.Identity.PublicKey) { + return nil + } + + c.config.Logger.Debug("received contact code advertisement", zap.Any("advertisement", message)) + for _, info := range message.PushNotificationInfo { + c.config.Logger.Debug("handling push notification query info") + serverPublicKey, err := crypto.DecompressPubkey(info.ServerPublicKey) + if err != nil { + c.config.Logger.Error("could not unmarshal server pubkey", zap.Binary("server-key", info.ServerPublicKey)) + return err + } + err = c.processQueryInfo(clientPublicKey, serverPublicKey, info) + if err != nil { + return err + } + } + + // Save query so that we won't query again to early + // NOTE: this is not very accurate as we might fetch an historical message, + // prolonging the time that we fetch new info. + // Most of the times it should work fine, as if the info are stale they'd be + // fetched again because of an error response from the push notification server + return c.persistence.SavePushNotificationQuery(clientPublicKey, []byte(uuid.New().String())) } // HandlePushNotificationResponse should set the request as processed @@ -427,6 +515,10 @@ func (c *Client) GetPushNotificationInfo(publicKey *ecdsa.PublicKey, installatio return c.persistence.GetPushNotificationInfo(publicKey, installationIDs) } +func (c *Client) Enabled() bool { + return c.config.RemoteNotificationsEnabled +} + func (c *Client) EnableSending() { c.config.SendEnabled = true } @@ -436,17 +528,21 @@ func (c *Client) DisableSending() { } func (c *Client) EnablePushNotificationsFromContactsOnly(contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error { + c.config.Logger.Debug("enabling push notification from contacts only") c.config.AllowFromContactsOnly = true - if c.lastPushNotificationRegistration != nil { - return c.Register(c.deviceToken, contactIDs, mutedChatIDs) + if c.lastPushNotificationRegistration != nil && c.config.RemoteNotificationsEnabled { + c.config.Logger.Debug("re-registering after enabling push notifications from contacts only") + return c.Register(c.deviceToken, c.apnTopic, c.tokenType, contactIDs, mutedChatIDs) } return nil } func (c *Client) DisablePushNotificationsFromContactsOnly(contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error { + c.config.Logger.Debug("disabling push notification from contacts only") c.config.AllowFromContactsOnly = false - if c.lastPushNotificationRegistration != nil { - return c.Register(c.deviceToken, contactIDs, mutedChatIDs) + if c.lastPushNotificationRegistration != nil && c.config.RemoteNotificationsEnabled { + c.config.Logger.Debug("re-registering after disabling push notifications from contacts only") + return c.Register(c.deviceToken, c.apnTopic, c.tokenType, contactIDs, mutedChatIDs) } return nil } @@ -545,8 +641,9 @@ func (c *Client) loadLastPushNotificationRegistration() error { c.lastContactIDs = lastContactIDs c.lastPushNotificationRegistration = lastRegistration c.deviceToken = lastRegistration.DeviceToken + c.apnTopic = lastRegistration.ApnTopic + c.tokenType = lastRegistration.TokenType return nil - } func (c *Client) stopRegistrationLoop() { @@ -807,7 +904,7 @@ func (c *Client) allowedKeyList(token []byte, contactIDs []*ecdsa.PublicKey) ([] // and return a new one in that case. A token is refreshed only if it's not set // or if a contact has been removed func (c *Client) getToken(contactIDs []*ecdsa.PublicKey) string { - if c.lastPushNotificationRegistration == nil || len(c.lastPushNotificationRegistration.AccessToken) == 0 || c.shouldRefreshToken(c.lastContactIDs, contactIDs) { + if c.lastPushNotificationRegistration == nil || len(c.lastPushNotificationRegistration.AccessToken) == 0 || c.shouldRefreshToken(c.lastContactIDs, contactIDs, c.lastPushNotificationRegistration.AllowFromContactsOnly, c.config.AllowFromContactsOnly) { c.config.Logger.Info("refreshing access token") return uuid.New().String() } @@ -830,7 +927,8 @@ func (c *Client) buildPushNotificationRegistrationMessage(contactIDs []*ecdsa.Pu options := &protobuf.PushNotificationRegistration{ AccessToken: token, - TokenType: c.config.TokenType, + TokenType: c.tokenType, + ApnTopic: c.apnTopic, Version: c.getVersion(), InstallationId: c.config.InstallationID, DeviceToken: c.deviceToken, @@ -851,8 +949,17 @@ func (c *Client) buildPushNotificationUnregisterMessage() *protobuf.PushNotifica return options } -// shouldRefreshToken tells us whether we should pull a new token, that's only necessary when a contact is removed -func (c *Client) shouldRefreshToken(oldContactIDs, newContactIDs []*ecdsa.PublicKey) bool { +// shouldRefreshToken tells us whether we should create a new token, +// that's only necessary when a contact is removed +// or allowFromContactsOnly is enabled. +// In both cases we want to invalidate any existing token +func (c *Client) shouldRefreshToken(oldContactIDs, newContactIDs []*ecdsa.PublicKey, oldAllowFromContactsOnly, newAllowFromContactsOnly bool) bool { + + // Check if allowFromContactsOnly has just been enabled + if !oldAllowFromContactsOnly && newAllowFromContactsOnly { + return true + } + newContactIDsMap := make(map[string]bool) for _, pk := range newContactIDs { newContactIDsMap[types.EncodeHex(crypto.FromECDSAPub(pk))] = true @@ -1017,6 +1124,7 @@ func (c *Client) sendNotification(publicKey *ecdsa.PublicKey, installationIDs [] for _, i := range infos { // TODO: Add ChatID, message, public_key pushNotifications = append(pushNotifications, &protobuf.PushNotification{ + Type: protobuf.PushNotification_MESSAGE, AccessToken: i.AccessToken, PublicKey: common.HashPublicKey(publicKey), InstallationId: i.InstallationID, @@ -1193,7 +1301,6 @@ func (c *Client) saveLastPushNotificationRegistration(registration *protobuf.Pus c.lastPushNotificationRegistration = registration c.lastContactIDs = contactIDs - c.startRegistrationLoop() return nil } @@ -1245,6 +1352,48 @@ func (c *Client) handleAllowedKeyList(publicKey *ecdsa.PublicKey, allowedKeyList return "" } +func (c *Client) MyPushNotificationQueryInfo() ([]*protobuf.PushNotificationQueryInfo, error) { + + // Nothing to do + if c.lastPushNotificationRegistration == nil || c.lastPushNotificationRegistration.Unregister { + return nil, nil + + } + var response []*protobuf.PushNotificationQueryInfo + servers, err := c.persistence.GetServers() + if err != nil { + return nil, err + } + for _, server := range servers { + // ignore non-registered servers + if !server.Registered { + continue + } + // build grant for this specific server + grant, err := c.buildGrantSignature(server.PublicKey, c.lastPushNotificationRegistration.AccessToken) + if err != nil { + c.config.Logger.Error("failed to build grant", zap.Error(err)) + return nil, err + } + + queryInfo := &protobuf.PushNotificationQueryInfo{ + InstallationId: c.config.InstallationID, + // is this the right key? + PublicKey: common.HashPublicKey(&c.config.Identity.PublicKey), + Version: c.lastPushNotificationRegistration.Version, + Grant: grant, + ServerPublicKey: crypto.CompressPubkey(server.PublicKey), + } + if c.lastPushNotificationRegistration.AllowFromContactsOnly { + queryInfo.AllowedKeyList = c.lastPushNotificationRegistration.AllowedKeyList + } else { + queryInfo.AccessToken = c.lastPushNotificationRegistration.AccessToken + } + response = append(response, queryInfo) + } + return response, nil +} + // queryPushNotificationInfo sends a message to any server who has the given user registered. // it uses an ephemeral key so the identity of the client querying is not disclosed func (c *Client) queryPushNotificationInfo(publicKey *ecdsa.PublicKey) error { diff --git a/protocol/pushnotificationclient/client_test.go b/protocol/pushnotificationclient/client_test.go index 1c372fdbe..ed6613024 100644 --- a/protocol/pushnotificationclient/client_test.go +++ b/protocol/pushnotificationclient/client_test.go @@ -219,12 +219,17 @@ func (s *ClientSuite) TestShouldRefreshToken() { s.Require().NoError(err) // Contacts are added - s.Require().False(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey, &key3.PublicKey, &key4.PublicKey})) + s.Require().False(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey, &key3.PublicKey, &key4.PublicKey}, true, true)) // everything the same - s.Require().False(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey, &key1.PublicKey})) + s.Require().False(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey, &key1.PublicKey}, true, true)) // A contact is removed - s.Require().True(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey})) + s.Require().True(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey}, true, true)) + // allow from contacts only is disabled + s.Require().False(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey, &key1.PublicKey}, true, false)) + + // allow from contacts only is enabled + s.Require().True(s.client.shouldRefreshToken([]*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey}, []*ecdsa.PublicKey{&key2.PublicKey, &key1.PublicKey}, false, true)) } diff --git a/protocol/pushnotificationserver/gorush.go b/protocol/pushnotificationserver/gorush.go index e791583b0..10343630b 100644 --- a/protocol/pushnotificationserver/gorush.go +++ b/protocol/pushnotificationserver/gorush.go @@ -4,8 +4,11 @@ import ( "bytes" "encoding/hex" "encoding/json" + "io/ioutil" "net/http" + "go.uber.org/zap" + "github.com/status-im/status-go/protocol/protobuf" ) @@ -21,6 +24,7 @@ type GoRushRequestNotification struct { Tokens []string `json:"tokens"` Platform uint `json:"platform"` Message string `json:"message"` + Topic string `json:"topic"` Data *GoRushRequestData `json:"data"` } @@ -53,6 +57,7 @@ func PushNotificationRegistrationToGoRushRequest(requestAndRegistrations []*Requ Tokens: []string{registration.DeviceToken}, Platform: tokenTypeToGoRushPlatform(registration.TokenType), Message: defaultNotificationMessage, + Topic: registration.ApnTopic, Data: &GoRushRequestData{ EncryptedMessage: hex.EncodeToString(request.Message), ChatID: request.ChatId, @@ -63,15 +68,20 @@ func PushNotificationRegistrationToGoRushRequest(requestAndRegistrations []*Requ return goRushRequests } -func sendGoRushNotification(request *GoRushRequest, url string) error { +func sendGoRushNotification(request *GoRushRequest, url string, logger *zap.Logger) error { payload, err := json.Marshal(request) if err != nil { return err } - _, err = http.Post(url+"/api/push", "application/json", bytes.NewReader(payload)) + response, err := http.Post(url+"/api/push", "application/json", bytes.NewReader(payload)) if err != nil { return err } + defer response.Body.Close() + body, _ := ioutil.ReadAll(response.Body) + + logger.Info("Sent gorush request", zap.String("response", string(body))) + return nil } diff --git a/protocol/pushnotificationserver/persistence.go b/protocol/pushnotificationserver/persistence.go index 0f31fb178..7ba19d99b 100644 --- a/protocol/pushnotificationserver/persistence.go +++ b/protocol/pushnotificationserver/persistence.go @@ -16,9 +16,14 @@ type Persistence interface { GetPushNotificationRegistrationByPublicKeyAndInstallationID(publicKey []byte, installationID string) (*protobuf.PushNotificationRegistration, error) // GetPushNotificationRegistrationByPublicKey retrieve all the push notification registrations from storage given a public key GetPushNotificationRegistrationByPublicKeys(publicKeys [][]byte) ([]*PushNotificationIDAndRegistration, error) - //GetPushNotificationRegistrationPublicKeys return all the public keys stored + // GetPushNotificationRegistrationPublicKeys return all the public keys stored GetPushNotificationRegistrationPublicKeys() ([][]byte, error) + //GetPushNotificationRegistrationVersion returns the latest version or 0 for a given pk and installationID + GetPushNotificationRegistrationVersion(publicKey []byte, installationID string) (uint64, error) + // UnregisterPushNotificationRegistration unregister a given pk/installationID + UnregisterPushNotificationRegistration(publicKey []byte, installationID string, version uint64) error + // DeletePushNotificationRegistration deletes a push notification registration from storage given a public key and installation id DeletePushNotificationRegistration(publicKey []byte, installationID string) error // SavePushNotificationRegistration saves a push notification option to the db @@ -39,7 +44,7 @@ func NewSQLitePersistence(db *sql.DB) Persistence { func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeyAndInstallationID(publicKey []byte, installationID string) (*protobuf.PushNotificationRegistration, error) { var marshaledRegistration []byte - err := p.db.QueryRow(`SELECT registration FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ?`, publicKey, installationID).Scan(&marshaledRegistration) + err := p.db.QueryRow(`SELECT registration FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ? AND registration IS NOT NULL`, publicKey, installationID).Scan(&marshaledRegistration) if err == sql.ErrNoRows { return nil, nil @@ -48,13 +53,25 @@ func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeyAndInstall } registration := &protobuf.PushNotificationRegistration{} - if err := proto.Unmarshal(marshaledRegistration, registration); err != nil { return nil, err } return registration, nil } +func (p *SQLitePersistence) GetPushNotificationRegistrationVersion(publicKey []byte, installationID string) (uint64, error) { + var version uint64 + err := p.db.QueryRow(`SELECT version FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ?`, publicKey, installationID).Scan(&version) + + if err == sql.ErrNoRows { + return 0, nil + } else if err != nil { + return 0, err + } + + return version, nil +} + type PushNotificationIDAndRegistration struct { ID []byte Registration *protobuf.PushNotificationRegistration @@ -70,7 +87,7 @@ func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeys(publicKe inVector := strings.Repeat("?, ", len(publicKeys)-1) + "?" - rows, err := p.db.Query(`SELECT public_key,registration FROM push_notification_server_registrations WHERE public_key IN (`+inVector+`)`, publicKeyArgs...) // nolint: gosec + rows, err := p.db.Query(`SELECT public_key,registration FROM push_notification_server_registrations WHERE registration IS NOT NULL AND public_key IN (`+inVector+`)`, publicKeyArgs...) // nolint: gosec if err != nil { return nil, err } @@ -86,6 +103,10 @@ func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeys(publicKe } registration := &protobuf.PushNotificationRegistration{} + // Skip if there's no registration + if marshaledRegistration == nil { + continue + } if err := proto.Unmarshal(marshaledRegistration, registration); err != nil { return nil, err @@ -97,7 +118,7 @@ func (p *SQLitePersistence) GetPushNotificationRegistrationByPublicKeys(publicKe } func (p *SQLitePersistence) GetPushNotificationRegistrationPublicKeys() ([][]byte, error) { - rows, err := p.db.Query(`SELECT public_key FROM push_notification_server_registrations`) + rows, err := p.db.Query(`SELECT public_key FROM push_notification_server_registrations WHERE registration IS NOT NULL`) if err != nil { return nil, err } @@ -126,6 +147,11 @@ func (p *SQLitePersistence) SavePushNotificationRegistration(publicKey []byte, r return err } +func (p *SQLitePersistence) UnregisterPushNotificationRegistration(publicKey []byte, installationID string, version uint64) error { + _, err := p.db.Exec(`UPDATE push_notification_server_registrations SET registration = NULL, version = ? WHERE public_key = ? AND installation_id = ?`, version, publicKey, installationID) + return err +} + func (p *SQLitePersistence) DeletePushNotificationRegistration(publicKey []byte, installationID string) error { _, err := p.db.Exec(`DELETE FROM push_notification_server_registrations WHERE public_key = ? AND installation_id = ?`, publicKey, installationID) return err diff --git a/protocol/pushnotificationserver/server.go b/protocol/pushnotificationserver/server.go index 904982a7f..794c5d18e 100644 --- a/protocol/pushnotificationserver/server.go +++ b/protocol/pushnotificationserver/server.go @@ -233,12 +233,12 @@ func (s *Server) validateRegistration(publicKey *ecdsa.PublicKey, payload []byte return nil, ErrMalformedPushNotificationRegistrationInstallationID } - previousRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(publicKey), registration.InstallationId) + previousVersion, err := s.persistence.GetPushNotificationRegistrationVersion(common.HashPublicKey(publicKey), registration.InstallationId) if err != nil { return nil, err } - if previousRegistration != nil && registration.Version <= previousRegistration.Version { + if registration.Version <= previousVersion { return nil, ErrInvalidPushNotificationRegistrationVersion } @@ -290,6 +290,7 @@ func (s *Server) buildPushNotificationQueryResponse(query *protobuf.PushNotifica for _, idAndResponse := range registrations { registration := idAndResponse.Registration + info := &protobuf.PushNotificationQueryInfo{ PublicKey: idAndResponse.ID, Grant: registration.Grant, @@ -333,6 +334,11 @@ func (s *Server) buildPushNotificationRequestResponseAndSendNotification(request InstallationId: pn.InstallationId, } + if pn.Type != protobuf.PushNotification_MESSAGE { + s.config.Logger.Warn("unhandled type") + continue + } + if err != nil { s.config.Logger.Error("failed to retrieve registration", zap.Error(err)) report.Error = protobuf.PushNotificationReport_UNKNOWN_ERROR_TYPE @@ -340,7 +346,6 @@ func (s *Server) buildPushNotificationRequestResponseAndSendNotification(request s.config.Logger.Warn("empty registration") report.Error = protobuf.PushNotificationReport_NOT_REGISTERED } else if registration.AccessToken != pn.AccessToken { - s.config.Logger.Warn("invalid access token") report.Error = protobuf.PushNotificationReport_WRONG_TOKEN } else { // For now we just assume that the notification will be successful @@ -362,7 +367,7 @@ func (s *Server) buildPushNotificationRequestResponseAndSendNotification(request // This can be done asynchronously goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations) - err := sendGoRushNotification(goRushRequest, s.config.GorushURL) + err := sendGoRushNotification(goRushRequest, s.config.GorushURL, s.config.Logger) if err != nil { s.config.Logger.Error("failed to send go rush notification", zap.Error(err)) // TODO: handle this error? @@ -403,12 +408,9 @@ func (s *Server) buildPushNotificationRegistrationResponse(publicKey *ecdsa.Publ } if registration.Unregister { + s.config.Logger.Info("unregistering client") // We save an empty registration, only keeping version and installation-id - emptyRegistration := &protobuf.PushNotificationRegistration{ - Version: registration.Version, - InstallationId: registration.InstallationId, - } - if err := s.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), emptyRegistration); err != nil { + if err := s.persistence.UnregisterPushNotificationRegistration(common.HashPublicKey(publicKey), registration.InstallationId, registration.Version); err != nil { response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR s.config.Logger.Error("failed to unregister ", zap.Error(err)) return response diff --git a/protocol/pushnotificationserver/server_test.go b/protocol/pushnotificationserver/server_test.go index 77613ce50..3cdde09a6 100644 --- a/protocol/pushnotificationserver/server_test.go +++ b/protocol/pushnotificationserver/server_test.go @@ -500,16 +500,16 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() { response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext) s.Require().NotNil(response) s.Require().True(response.Success) + s.Require().Equal(common.Shake256(cyphertext), response.RequestId) // Check is gone from the db retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID) s.Require().NoError(err) - s.Require().NotNil(retrievedRegistration) - s.Require().Empty(retrievedRegistration.AccessToken) - s.Require().Empty(retrievedRegistration.DeviceToken) - s.Require().Equal(uint64(2), retrievedRegistration.Version) - s.Require().Equal(s.installationID, retrievedRegistration.InstallationId) - s.Require().Equal(common.Shake256(cyphertext), response.RequestId) + s.Require().Nil(retrievedRegistration) + // Check version is maintained + version, err := s.persistence.GetPushNotificationRegistrationVersion(common.HashPublicKey(&s.key.PublicKey), s.installationID) + s.Require().NoError(err) + s.Require().Equal(uint64(2), version) } func (s *ServerSuite) TestbuildPushNotificationQueryResponseNoFiltering() { diff --git a/protocol/v1/status_message.go b/protocol/v1/status_message.go index 456b97587..84ad468ae 100644 --- a/protocol/v1/status_message.go +++ b/protocol/v1/status_message.go @@ -14,6 +14,8 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/datasync" "github.com/status-im/status-go/protocol/encryption" + "github.com/status-im/status-go/protocol/encryption/multidevice" + "github.com/status-im/status-go/protocol/encryption/sharedsecret" "github.com/status-im/status-go/protocol/protobuf" ) @@ -45,6 +47,11 @@ type StatusMessage struct { TransportLayerSigPubKey *ecdsa.PublicKey `json:"-"` // ApplicationMetadataLayerPubKey contains the public key provided by the application metadata layer ApplicationMetadataLayerSigPubKey *ecdsa.PublicKey `json:"-"` + + // Installations is the new installations returned by the encryption layer + Installations []*multidevice.Installation + // SharedSecret is the shared secret returned by the encryption layer + SharedSecrets []*sharedsecret.Secret } // Temporary JSON marshaling for those messages that are not yet processed @@ -117,7 +124,7 @@ func (m *StatusMessage) HandleEncryption(myKey *ecdsa.PrivateKey, senderKey *ecd return errors.Wrap(err, "failed to unmarshal ProtocolMessage") } - payload, err := enc.HandleMessage( + response, err := enc.HandleMessage( myKey, senderKey, &protocolMessage, @@ -128,7 +135,9 @@ func (m *StatusMessage) HandleEncryption(myKey *ecdsa.PrivateKey, senderKey *ecd return errors.Wrap(err, "failed to handle Encryption message") } - m.DecryptedPayload = payload + m.DecryptedPayload = response.DecryptedMessage + m.Installations = response.Installations + m.SharedSecrets = response.SharedSecrets return nil } @@ -217,7 +226,6 @@ func (m *StatusMessage) HandleApplication() error { case protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION: return m.unmarshalProtobufData(new(protobuf.PairInstallation)) - case protobuf.ApplicationMetadataMessage_CONTACT_CODE_ADVERTISEMENT: return m.unmarshalProtobufData(new(protobuf.ContactCodeAdvertisement)) case protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REQUEST: diff --git a/services/ext/api.go b/services/ext/api.go index ffa830c28..cb612cf60 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -430,7 +430,7 @@ func (api *PublicAPI) StopPushNotificationsServer() error { // PushNotification client endpoints -func (api *PublicAPI) RegisterForPushNotifications(ctx context.Context, deviceToken string) error { +func (api *PublicAPI) RegisterForPushNotifications(ctx context.Context, deviceToken string, apnTopic string, tokenType protobuf.PushNotificationRegistration_TokenType) error { // We set both for now as they are equivalent err := api.service.accountsDB.SaveSetting("remote-push-notifications-enabled?", true) if err != nil { @@ -441,10 +441,10 @@ func (api *PublicAPI) RegisterForPushNotifications(ctx context.Context, deviceTo return err } - return api.service.messenger.RegisterForPushNotifications(ctx, deviceToken) + return api.service.messenger.RegisterForPushNotifications(ctx, deviceToken, apnTopic, tokenType) } -func (api *PublicAPI) UnregisterForPushNotifications(ctx context.Context) error { +func (api *PublicAPI) UnregisterFromPushNotifications(ctx context.Context) error { err := api.service.accountsDB.SaveSetting("remote-push-notifications-enabled?", false) if err != nil { return err diff --git a/services/ext/service.go b/services/ext/service.go index c39741c0c..3aa4beddf 100644 --- a/services/ext/service.go +++ b/services/ext/service.go @@ -169,10 +169,14 @@ func (s *Service) InitProtocol(identity *ecdsa.PrivateKey, db *sql.DB, logger *z func (s *Service) StartMessenger() error { // Start a loop that retrieves all messages and propagates them to status-react. s.cancelMessenger = make(chan struct{}) + err := s.messenger.Start() + if err != nil { + return err + } go s.retrieveMessagesLoop(time.Second, s.cancelMessenger) go s.verifyTransactionLoop(30*time.Second, s.cancelMessenger) go s.verifyENSLoop(30*time.Second, s.cancelMessenger) - return s.messenger.Start() + return nil } func (s *Service) retrieveMessagesLoop(tick time.Duration, cancel <-chan struct{}) { @@ -456,6 +460,7 @@ func buildMessengerOptions( ) ([]protocol.Option, error) { options := []protocol.Option{ protocol.WithCustomLogger(logger), + protocol.WithPushNotifications(), protocol.WithDatabase(db), protocol.WithEnvelopesMonitorConfig(envelopesMonitorConfig), protocol.WithOnNegotiatedFilters(onNegotiatedFilters),