github.com/status-im/status-go@v1.1.0/protocol/encryption/multidevice/persistence.go (about)

     1  package multidevice
     2  
     3  import "database/sql"
     4  
     5  type sqlitePersistence struct {
     6  	db *sql.DB
     7  }
     8  
     9  func newSQLitePersistence(db *sql.DB) *sqlitePersistence {
    10  	return &sqlitePersistence{db: db}
    11  }
    12  
    13  // GetActiveInstallations returns the active installations for a given identity
    14  func (s *sqlitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) {
    15  	stmt, err := s.db.Prepare(`SELECT installation_id, version
    16  				   FROM installations
    17  				   WHERE enabled = 1 AND identity = ?
    18  				   ORDER BY timestamp DESC
    19  				   LIMIT ?`)
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  
    24  	var installations []*Installation
    25  	rows, err := stmt.Query(identity, maxInstallations)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	defer rows.Close()
    30  
    31  	for rows.Next() {
    32  		var (
    33  			installationID string
    34  			version        uint32
    35  		)
    36  		err = rows.Scan(
    37  			&installationID,
    38  			&version,
    39  		)
    40  		if err != nil {
    41  			return nil, err
    42  		}
    43  		installations = append(installations, &Installation{
    44  			ID:      installationID,
    45  			Version: version,
    46  			Enabled: true,
    47  		})
    48  
    49  	}
    50  
    51  	return installations, nil
    52  
    53  }
    54  
    55  // GetInstallations returns all the installations for a given identity
    56  // we both return the installations & the metadata
    57  // metadata is currently stored in a separate table, as in some cases we
    58  // might have metadata for a device, but no other information on the device
    59  func (s *sqlitePersistence) GetInstallations(identity []byte) ([]*Installation, error) {
    60  	installationMap := make(map[string]*Installation)
    61  	var installations []*Installation
    62  
    63  	// We query both tables as sqlite does not support full outer joins
    64  	installationsStmt, err := s.db.Prepare(`SELECT installation_id, version, enabled, timestamp FROM installations WHERE identity = ?`)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	defer installationsStmt.Close()
    69  
    70  	installationRows, err := installationsStmt.Query(identity)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	for installationRows.Next() {
    76  		var installation Installation
    77  		err = installationRows.Scan(
    78  			&installation.ID,
    79  			&installation.Version,
    80  			&installation.Enabled,
    81  			&installation.Timestamp,
    82  		)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		// We initialized to empty in this case as we want to
    87  		// return metadata as well in this endpoint, but not in others
    88  		installation.InstallationMetadata = &InstallationMetadata{}
    89  		installationMap[installation.ID] = &installation
    90  	}
    91  
    92  	metadataStmt, err := s.db.Prepare(`SELECT installation_id, name, device_type, fcm_token FROM installation_metadata WHERE identity = ?`)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defer metadataStmt.Close()
    97  
    98  	metadataRows, err := metadataStmt.Query(identity)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	for metadataRows.Next() {
   104  		var (
   105  			installationID string
   106  			name           sql.NullString
   107  			deviceType     sql.NullString
   108  			fcmToken       sql.NullString
   109  			installation   *Installation
   110  		)
   111  		err = metadataRows.Scan(
   112  			&installationID,
   113  			&name,
   114  			&deviceType,
   115  			&fcmToken,
   116  		)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		if _, ok := installationMap[installationID]; ok {
   121  			installation = installationMap[installationID]
   122  		} else {
   123  			installation = &Installation{ID: installationID}
   124  		}
   125  		installation.InstallationMetadata = &InstallationMetadata{
   126  			Name:       name.String,
   127  			DeviceType: deviceType.String,
   128  			FCMToken:   fcmToken.String,
   129  		}
   130  		installationMap[installationID] = installation
   131  	}
   132  
   133  	for _, installation := range installationMap {
   134  		installations = append(installations, installation)
   135  	}
   136  
   137  	return installations, nil
   138  }
   139  
   140  // AddInstallations adds the installations for a given identity, maintaining the enabled flag
   141  func (s *sqlitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
   142  	tx, err := s.db.Begin()
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	var insertedInstallations []*Installation
   148  
   149  	for _, installation := range installations {
   150  		stmt, err := tx.Prepare(`SELECT enabled, version
   151  					 FROM installations
   152  					 WHERE identity = ? AND installation_id = ?
   153  					 LIMIT 1`)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		defer stmt.Close()
   158  
   159  		var oldEnabled bool
   160  		// We don't override version once we saw one
   161  		var oldVersion uint32
   162  		latestVersion := installation.Version
   163  
   164  		err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
   165  		if err != nil && err != sql.ErrNoRows {
   166  			return nil, err
   167  		}
   168  
   169  		if err == sql.ErrNoRows {
   170  			stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
   171  						VALUES (?, ?, ?, ?, ?)`)
   172  			if err != nil {
   173  				return nil, err
   174  			}
   175  			defer stmt.Close()
   176  
   177  			_, err = stmt.Exec(
   178  				identity,
   179  				installation.ID,
   180  				timestamp,
   181  				defaultEnabled,
   182  				latestVersion,
   183  			)
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  			insertedInstallations = append(insertedInstallations, installation)
   188  		} else {
   189  			// We update timestamp if present without changing enabled, only if this is a new bundle
   190  			// and we set the version to the latest we ever saw
   191  			if oldVersion > installation.Version {
   192  				latestVersion = oldVersion
   193  			}
   194  
   195  			stmt, err = tx.Prepare(`UPDATE installations
   196  					        SET timestamp = ?,  enabled = ?, version = ?
   197  						WHERE identity = ?
   198  						AND installation_id = ?
   199  						AND timestamp < ?`)
   200  			if err != nil {
   201  				return nil, err
   202  			}
   203  			defer stmt.Close()
   204  
   205  			_, err = stmt.Exec(
   206  				timestamp,
   207  				oldEnabled,
   208  				latestVersion,
   209  				identity,
   210  				installation.ID,
   211  				timestamp,
   212  			)
   213  			if err != nil {
   214  				return nil, err
   215  			}
   216  		}
   217  
   218  	}
   219  
   220  	if err := tx.Commit(); err != nil {
   221  		_ = tx.Rollback()
   222  		return nil, err
   223  	}
   224  
   225  	return insertedInstallations, nil
   226  
   227  }
   228  
   229  // EnableInstallation enables the installation
   230  func (s *sqlitePersistence) EnableInstallation(identity []byte, installationID string) error {
   231  	stmt, err := s.db.Prepare(`UPDATE installations
   232  				   SET enabled = 1
   233  				   WHERE identity = ? AND installation_id = ?`)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	_, err = stmt.Exec(identity, installationID)
   239  	return err
   240  
   241  }
   242  
   243  // DisableInstallation disable the installation
   244  func (s *sqlitePersistence) DisableInstallation(identity []byte, installationID string) error {
   245  	stmt, err := s.db.Prepare(`UPDATE installations
   246  				   SET enabled = 0
   247  				   WHERE identity = ? AND installation_id = ?`)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	_, err = stmt.Exec(identity, installationID)
   253  	return err
   254  }
   255  
   256  // SetInstallationMetadata sets the metadata for a given installation
   257  func (s *sqlitePersistence) SetInstallationMetadata(identity []byte, installationID string, metadata *InstallationMetadata) error {
   258  	stmt, err := s.db.Prepare(`INSERT INTO installation_metadata(name, device_type, fcm_token, identity, installation_id) VALUES(?,?,?,?,?)`)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	_, err = stmt.Exec(metadata.Name, metadata.DeviceType, metadata.FCMToken, identity, installationID)
   264  	return err
   265  }
   266  
   267  // SetInstallationName sets the only the name in metadata for a given installation
   268  func (s *sqlitePersistence) SetInstallationName(identity []byte, installationID string, name string) error {
   269  	stmt, err := s.db.Prepare(`UPDATE installation_metadata 
   270  							   SET name = ? 
   271  							   WHERE identity = ? AND installation_id = ?`)
   272  	if err != nil {
   273  		return err
   274  	}
   275  
   276  	_, err = stmt.Exec(name, identity, installationID)
   277  	return err
   278  }