2020-07-22 07:41:40 +00:00
package pushnotificationclient
2020-07-07 09:00:04 +00:00
import (
2020-07-15 08:22:43 +00:00
"bytes"
2020-07-10 13:26:06 +00:00
"context"
2020-07-07 09:00:04 +00:00
"crypto/ecdsa"
"database/sql"
2020-07-15 08:22:43 +00:00
"encoding/gob"
2020-07-10 07:45:40 +00:00
"strings"
2020-07-10 13:26:06 +00:00
"time"
2020-07-07 09:00:04 +00:00
2020-07-15 07:23:31 +00:00
"github.com/golang/protobuf/proto"
2020-07-07 09:00:04 +00:00
"github.com/status-im/status-go/eth-node/crypto"
2020-07-15 07:23:31 +00:00
"github.com/status-im/status-go/protocol/protobuf"
2020-07-07 09:00:04 +00:00
)
type Persistence struct {
db * sql . DB
}
func NewPersistence ( db * sql . DB ) * Persistence {
return & Persistence { db : db }
}
2020-07-15 08:22:43 +00:00
func ( p * Persistence ) GetLastPushNotificationRegistration ( ) ( * protobuf . PushNotificationRegistration , [ ] * ecdsa . PublicKey , error ) {
2020-07-15 07:23:31 +00:00
var registrationBytes [ ] byte
2020-07-15 08:22:43 +00:00
var contactIDsBytes [ ] byte
err := p . db . QueryRow ( ` SELECT registration,contact_ids FROM push_notification_client_registrations LIMIT 1 ` ) . Scan ( & registrationBytes , & contactIDsBytes )
2020-07-15 07:23:31 +00:00
if err == sql . ErrNoRows {
2020-07-15 08:22:43 +00:00
return nil , nil , nil
2020-07-15 07:23:31 +00:00
} else if err != nil {
2020-07-15 08:22:43 +00:00
return nil , nil , err
}
var publicKeyBytes [ ] [ ] byte
var contactIDs [ ] * ecdsa . PublicKey
// Restore contactIDs
contactIDsDecoder := gob . NewDecoder ( bytes . NewBuffer ( contactIDsBytes ) )
err = contactIDsDecoder . Decode ( & publicKeyBytes )
if err != nil {
return nil , nil , err
}
for _ , pkBytes := range publicKeyBytes {
2020-07-20 10:01:42 +00:00
pk , err := crypto . DecompressPubkey ( pkBytes )
2020-07-15 08:22:43 +00:00
if err != nil {
return nil , nil , err
}
contactIDs = append ( contactIDs , pk )
2020-07-15 07:23:31 +00:00
}
registration := & protobuf . PushNotificationRegistration { }
err = proto . Unmarshal ( registrationBytes , registration )
if err != nil {
2020-07-15 08:22:43 +00:00
return nil , nil , err
2020-07-15 07:23:31 +00:00
}
2020-07-15 08:22:43 +00:00
return registration , contactIDs , nil
2020-07-15 07:23:31 +00:00
}
2020-07-15 08:22:43 +00:00
func ( p * Persistence ) SaveLastPushNotificationRegistration ( registration * protobuf . PushNotificationRegistration , contactIDs [ ] * ecdsa . PublicKey ) error {
var encodedContactIDs bytes . Buffer
var contactIDsBytes [ ] [ ] byte
for _ , pk := range contactIDs {
2020-07-20 10:01:42 +00:00
contactIDsBytes = append ( contactIDsBytes , crypto . CompressPubkey ( pk ) )
2020-07-15 08:22:43 +00:00
}
pkEncoder := gob . NewEncoder ( & encodedContactIDs )
if err := pkEncoder . Encode ( contactIDsBytes ) ; err != nil {
return err
}
2020-07-15 07:23:31 +00:00
marshaledRegistration , err := proto . Marshal ( registration )
if err != nil {
return err
}
2020-07-15 08:22:43 +00:00
_ , err = p . db . Exec ( ` INSERT INTO push_notification_client_registrations (registration,contact_ids) VALUES (?, ?) ` , marshaledRegistration , encodedContactIDs . Bytes ( ) )
2020-07-15 07:23:31 +00:00
return err
}
2020-07-10 13:26:06 +00:00
func ( p * Persistence ) TrackPushNotification ( chatID string , messageID [ ] byte ) error {
trackedAt := time . Now ( ) . Unix ( )
_ , err := p . db . Exec ( ` INSERT INTO push_notification_client_tracked_messages (chat_id, message_id, tracked_at) VALUES (?,?,?) ` , chatID , messageID , trackedAt )
return err
}
2020-07-14 14:07:19 +00:00
func ( p * Persistence ) TrackedMessage ( messageID [ ] byte ) ( bool , error ) {
var count uint64
err := p . db . QueryRow ( ` SELECT COUNT(1) FROM push_notification_client_tracked_messages WHERE message_id = ? ` , messageID ) . Scan ( & count )
if err != nil {
return false , err
}
if count == 0 {
return false , nil
}
return true , nil
}
2020-07-10 13:26:06 +00:00
func ( p * Persistence ) SavePushNotificationQuery ( publicKey * ecdsa . PublicKey , queryID [ ] byte ) error {
queriedAt := time . Now ( ) . Unix ( )
_ , err := p . db . Exec ( ` INSERT INTO push_notification_client_queries (public_key, query_id, queried_at) VALUES (?,?,?) ` , crypto . CompressPubkey ( publicKey ) , queryID , queriedAt )
return err
}
func ( p * Persistence ) GetQueriedAt ( publicKey * ecdsa . PublicKey ) ( int64 , error ) {
var queriedAt int64
err := p . db . QueryRow ( ` SELECT queried_at FROM push_notification_client_queries WHERE public_key = ? ORDER BY queried_at DESC LIMIT 1 ` , crypto . CompressPubkey ( publicKey ) ) . Scan ( & queriedAt )
if err == sql . ErrNoRows {
return 0 , nil
}
if err != nil {
return 0 , err
}
return queriedAt , nil
2020-07-07 09:00:04 +00:00
}
2020-07-10 13:26:06 +00:00
func ( p * Persistence ) GetQueryPublicKey ( queryID [ ] byte ) ( * ecdsa . PublicKey , error ) {
var publicKeyBytes [ ] byte
err := p . db . QueryRow ( ` SELECT public_key FROM push_notification_client_queries WHERE query_id = ? ` , queryID ) . Scan ( & publicKeyBytes )
if err == sql . ErrNoRows {
return nil , nil
}
if err != nil {
return nil , err
}
publicKey , err := crypto . DecompressPubkey ( publicKeyBytes )
if err != nil {
return nil , err
}
return publicKey , nil
2020-07-07 09:00:04 +00:00
}
2020-07-10 13:26:06 +00:00
func ( p * Persistence ) SavePushNotificationInfo ( infos [ ] * PushNotificationInfo ) error {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
for _ , info := range infos {
2020-07-17 11:41:49 +00:00
var latestVersion uint64
clientCompressedKey := crypto . CompressPubkey ( info . PublicKey )
err := tx . QueryRow ( ` SELECT IFNULL(MAX(version),0) FROM push_notification_client_info WHERE public_key = ? AND installation_id = ? LIMIT 1 ` , clientCompressedKey , info . InstallationID ) . Scan ( & latestVersion )
if err != sql . ErrNoRows && err != nil {
return err
}
if latestVersion > info . Version {
// Nothing to do
continue
}
// Remove anything that as a lower version
_ , err = tx . Exec ( ` DELETE FROM push_notification_client_info WHERE public_key = ? AND installation_id = ? AND version < ? ` , clientCompressedKey , info . InstallationID , info . Version )
if err != nil {
return err
}
// Insert
_ , err = tx . Exec ( ` INSERT INTO push_notification_client_info (public_key, server_public_key, installation_id, access_token, retrieved_at, version) VALUES (?, ?, ?, ?, ?,?) ` , clientCompressedKey , crypto . CompressPubkey ( info . ServerPublicKey ) , info . InstallationID , info . AccessToken , info . RetrievedAt , info . Version )
2020-07-10 13:26:06 +00:00
if err != nil {
return err
}
}
2020-07-07 09:00:04 +00:00
return nil
}
2020-07-10 13:26:06 +00:00
func ( p * Persistence ) GetPushNotificationInfo ( publicKey * ecdsa . PublicKey , installationIDs [ ] string ) ( [ ] * PushNotificationInfo , error ) {
queryArgs := make ( [ ] interface { } , 0 , len ( installationIDs ) + 1 )
queryArgs = append ( queryArgs , crypto . CompressPubkey ( publicKey ) )
for _ , installationID := range installationIDs {
queryArgs = append ( queryArgs , installationID )
}
inVector := strings . Repeat ( "?, " , len ( installationIDs ) - 1 ) + "?"
2020-07-22 07:41:40 +00:00
rows , err := p . db . Query ( ` SELECT server_public_key, installation_id, version, access_token, retrieved_at FROM push_notification_client_info WHERE public_key = ? AND installation_id IN ( ` + inVector + ` ) ` , queryArgs ... ) //nolint: gosec
2020-07-10 13:26:06 +00:00
if err != nil {
return nil , err
}
2020-07-22 07:41:40 +00:00
defer rows . Close ( )
2020-07-10 13:26:06 +00:00
var infos [ ] * PushNotificationInfo
for rows . Next ( ) {
var serverPublicKeyBytes [ ] byte
info := & PushNotificationInfo { PublicKey : publicKey }
2020-07-17 11:41:49 +00:00
err := rows . Scan ( & serverPublicKeyBytes , & info . InstallationID , & info . Version , & info . AccessToken , & info . RetrievedAt )
2020-07-10 13:26:06 +00:00
if err != nil {
return nil , err
}
serverPublicKey , err := crypto . DecompressPubkey ( serverPublicKeyBytes )
if err != nil {
return nil , err
}
info . ServerPublicKey = serverPublicKey
infos = append ( infos , info )
}
return infos , nil
}
2020-07-14 14:07:19 +00:00
func ( p * Persistence ) GetPushNotificationInfoByPublicKey ( publicKey * ecdsa . PublicKey ) ( [ ] * PushNotificationInfo , error ) {
rows , err := p . db . Query ( ` SELECT server_public_key, installation_id, access_token, retrieved_at FROM push_notification_client_info WHERE public_key = ? ` , crypto . CompressPubkey ( publicKey ) )
if err != nil {
return nil , err
}
2020-07-22 07:41:40 +00:00
defer rows . Close ( )
2020-07-14 14:07:19 +00:00
var infos [ ] * PushNotificationInfo
for rows . Next ( ) {
var serverPublicKeyBytes [ ] byte
info := & PushNotificationInfo { PublicKey : publicKey }
err := rows . Scan ( & serverPublicKeyBytes , & info . InstallationID , & info . AccessToken , & info . RetrievedAt )
if err != nil {
return nil , err
}
serverPublicKey , err := crypto . DecompressPubkey ( serverPublicKeyBytes )
if err != nil {
return nil , err
}
info . ServerPublicKey = serverPublicKey
infos = append ( infos , info )
}
return infos , nil
}
func ( p * Persistence ) ShouldSendNotificationFor ( publicKey * ecdsa . PublicKey , installationID string , messageID [ ] byte ) ( bool , error ) {
2020-07-10 13:26:06 +00:00
// First we check that we are tracking this message, next we check that we haven't already sent this
var count uint64
err := p . db . QueryRow ( ` SELECT COUNT(1) FROM push_notification_client_tracked_messages WHERE message_id = ? ` , messageID ) . Scan ( & count )
if err != nil {
return false , err
}
if count == 0 {
return false , nil
}
err = p . db . QueryRow ( ` SELECT COUNT(1) FROM push_notification_client_sent_notifications WHERE message_id = ? AND public_key = ? AND installation_id = ? ` , messageID , crypto . CompressPubkey ( publicKey ) , installationID ) . Scan ( & count )
if err != nil {
return false , err
}
return count == 0 , nil
}
2020-07-14 14:07:19 +00:00
func ( p * Persistence ) ShouldSendNotificationToAllInstallationIDs ( publicKey * ecdsa . PublicKey , messageID [ ] byte ) ( bool , error ) {
// First we check that we are tracking this message, next we check that we haven't already sent this
var count uint64
err := p . db . QueryRow ( ` SELECT COUNT(1) FROM push_notification_client_tracked_messages WHERE message_id = ? ` , messageID ) . Scan ( & count )
if err != nil {
return false , err
}
if count == 0 {
return false , nil
}
err = p . db . QueryRow ( ` SELECT COUNT(1) FROM push_notification_client_sent_notifications WHERE message_id = ? AND public_key = ? ` , messageID , crypto . CompressPubkey ( publicKey ) ) . Scan ( & count )
if err != nil {
return false , err
}
return count == 0 , nil
}
2020-07-20 12:46:15 +00:00
func ( p * Persistence ) UpsertSentNotification ( n * SentNotification ) error {
2020-09-02 14:11:16 +00:00
_ , err := p . db . Exec ( ` INSERT INTO push_notification_client_sent_notifications (public_key, installation_id, message_id, last_tried_at, retry_count, success, error, hashed_public_key,chat_id, notification_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` , crypto . CompressPubkey ( n . PublicKey ) , n . InstallationID , n . MessageID , n . LastTriedAt , n . RetryCount , n . Success , n . Error , n . HashedPublicKey ( ) , n . ChatID , n . NotificationType )
2020-07-20 10:01:42 +00:00
return err
}
func ( p * Persistence ) GetSentNotification ( hashedPublicKey [ ] byte , installationID string , messageID [ ] byte ) ( * SentNotification , error ) {
var publicKeyBytes [ ] byte
sentNotification := & SentNotification {
InstallationID : installationID ,
MessageID : messageID ,
}
2020-09-02 14:11:16 +00:00
err := p . db . QueryRow ( ` SELECT retry_count, last_tried_at, error, success, public_key,chat_id,notification_type FROM push_notification_client_sent_notifications WHERE hashed_public_key = ? ` , hashedPublicKey ) . Scan ( & sentNotification . RetryCount , & sentNotification . LastTriedAt , & sentNotification . Error , & sentNotification . Success , & publicKeyBytes , & sentNotification . ChatID , & sentNotification . NotificationType )
2020-07-20 10:01:42 +00:00
if err != nil {
return nil , err
}
publicKey , err := crypto . DecompressPubkey ( publicKeyBytes )
if err != nil {
return nil , err
}
sentNotification . PublicKey = publicKey
return sentNotification , nil
}
func ( p * Persistence ) UpdateNotificationResponse ( messageID [ ] byte , response * protobuf . PushNotificationReport ) error {
_ , err := p . db . Exec ( ` UPDATE push_notification_client_sent_notifications SET success = ?, error = ? WHERE hashed_public_key = ? AND installation_id = ? AND message_id = ? AND NOT success ` , response . Success , response . Error , response . PublicKey , response . InstallationId , messageID )
2020-07-10 13:26:06 +00:00
return err
}
2020-07-20 12:46:15 +00:00
func ( p * Persistence ) GetRetriablePushNotifications ( ) ( [ ] * SentNotification , error ) {
var notifications [ ] * SentNotification
2020-09-02 14:11:16 +00:00
rows , err := p . db . Query ( ` SELECT retry_count, last_tried_at, error, success, public_key, installation_id, message_id,chat_id, notification_type FROM push_notification_client_sent_notifications WHERE NOT success AND error = ? ` , protobuf . PushNotificationReport_WRONG_TOKEN )
2020-07-20 12:46:15 +00:00
if err != nil {
return nil , err
}
2020-07-22 07:41:40 +00:00
defer rows . Close ( )
2020-07-20 12:46:15 +00:00
for rows . Next ( ) {
var publicKeyBytes [ ] byte
notification := & SentNotification { }
2020-09-02 14:11:16 +00:00
err = rows . Scan ( & notification . RetryCount , & notification . LastTriedAt , & notification . Error , & notification . Success , & publicKeyBytes , & notification . InstallationID , & notification . MessageID , & notification . ChatID , & notification . NotificationType )
2020-07-20 12:46:15 +00:00
if err != nil {
return nil , err
}
publicKey , err := crypto . DecompressPubkey ( publicKeyBytes )
if err != nil {
return nil , err
}
notification . PublicKey = publicKey
notifications = append ( notifications , notification )
}
return notifications , err
}
2020-07-07 09:00:04 +00:00
func ( p * Persistence ) UpsertServer ( server * PushNotificationServer ) error {
2020-08-20 09:05:39 +00:00
_ , err := p . db . Exec ( ` INSERT INTO push_notification_client_servers (public_key, registered, registered_at, access_token, last_retried_at, retry_count, server_type) VALUES (?,?,?,?,?,?,?) ` , crypto . CompressPubkey ( server . PublicKey ) , server . Registered , server . RegisteredAt , server . AccessToken , server . LastRetriedAt , server . RetryCount , server . Type )
2020-07-07 09:00:04 +00:00
return err
}
func ( p * Persistence ) GetServers ( ) ( [ ] * PushNotificationServer , error ) {
2020-08-20 09:05:39 +00:00
rows , err := p . db . Query ( ` SELECT public_key, registered, registered_at,access_token,last_retried_at, retry_count, server_type FROM push_notification_client_servers ` )
2020-07-07 09:00:04 +00:00
if err != nil {
return nil , err
}
2020-07-22 07:41:40 +00:00
defer rows . Close ( )
2020-07-07 09:00:04 +00:00
var servers [ ] * PushNotificationServer
for rows . Next ( ) {
server := & PushNotificationServer { }
var key [ ] byte
2020-08-20 09:05:39 +00:00
err := rows . Scan ( & key , & server . Registered , & server . RegisteredAt , & server . AccessToken , & server . LastRetriedAt , & server . RetryCount , & server . Type )
2020-07-07 09:00:04 +00:00
if err != nil {
return nil , err
}
parsedKey , err := crypto . DecompressPubkey ( key )
if err != nil {
return nil , err
}
2020-07-10 07:45:40 +00:00
server . PublicKey = parsedKey
servers = append ( servers , server )
}
return servers , nil
}
func ( p * Persistence ) GetServersByPublicKey ( keys [ ] * ecdsa . PublicKey ) ( [ ] * PushNotificationServer , error ) {
keyArgs := make ( [ ] interface { } , 0 , len ( keys ) )
for _ , key := range keys {
keyArgs = append ( keyArgs , crypto . CompressPubkey ( key ) )
}
inVector := strings . Repeat ( "?, " , len ( keys ) - 1 ) + "?"
rows , err := p . db . Query ( ` SELECT public_key, registered, registered_at,access_token FROM push_notification_client_servers WHERE public_key IN ( ` + inVector + ")" , keyArgs ... ) //nolint: gosec
if err != nil {
return nil , err
}
2020-07-22 07:41:40 +00:00
defer rows . Close ( )
2020-07-10 07:45:40 +00:00
var servers [ ] * PushNotificationServer
for rows . Next ( ) {
server := & PushNotificationServer { }
var key [ ] byte
err := rows . Scan ( & key , & server . Registered , & server . RegisteredAt , & server . AccessToken )
if err != nil {
return nil , err
}
parsedKey , err := crypto . DecompressPubkey ( key )
if err != nil {
return nil , err
}
server . PublicKey = parsedKey
2020-07-07 09:00:04 +00:00
servers = append ( servers , server )
}
return servers , nil
}