Move installations to status-go (#1499)

* Move installations to status-go

This commit moves installations management/storage to status-go.
We remove the native binding and provide RPC endpoints to set the
metadata and return a list of our own installations.
This commit is contained in:
Andrea Maria Piana 2019-06-26 11:32:59 +02:00 committed by GitHub
parent 0ade9a6cbb
commit 5335a2b4fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 456 additions and 200 deletions

View File

@ -1 +1 @@
0.27.0-beta.1
0.28.0-beta.0

View File

@ -658,46 +658,6 @@ func (b *StatusBackend) SignGroupMembership(content string) (string, error) {
return crypto.Sign(content, selectedChatAccount.AccountKey.PrivateKey)
}
// EnableInstallation enables an installation for multi-device sync.
func (b *StatusBackend) EnableInstallation(installationID string) error {
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
if err != nil {
return err
}
st, err := b.statusNode.ShhExtService()
if err != nil {
return err
}
if err := st.EnableInstallation(&selectedChatAccount.AccountKey.PrivateKey.PublicKey, installationID); err != nil {
b.log.Error("error enabling installation", "err", err)
return err
}
return nil
}
// DisableInstallation disables an installation for multi-device sync.
func (b *StatusBackend) DisableInstallation(installationID string) error {
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
if err != nil {
return err
}
st, err := b.statusNode.ShhExtService()
if err != nil {
return err
}
if err := st.DisableInstallation(&selectedChatAccount.AccountKey.PrivateKey.PublicKey, installationID); err != nil {
b.log.Error("error disabling installation", "err", err)
return err
}
return nil
}
// UpdateMailservers on ShhExtService.
func (b *StatusBackend) UpdateMailservers(enodes []string) error {
st, err := b.statusNode.ShhExtService()

View File

@ -93,42 +93,6 @@ func SignGroupMembership(content *C.char) *C.char {
return C.CString(string(data))
}
// EnableInstallation enables an installation for multi-device sync.
//export EnableInstallation
func EnableInstallation(installationID *C.char) *C.char {
err := statusBackend.EnableInstallation(C.GoString(installationID))
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return C.CString(string(data))
}
// DisableInstallation disables an installation for multi-device sync.
//export DisableInstallation
func DisableInstallation(installationID *C.char) *C.char {
err := statusBackend.DisableInstallation(C.GoString(installationID))
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return C.CString(string(data))
}
//ValidateNodeConfig validates config for status node
//export ValidateNodeConfig
func ValidateNodeConfig(configJSON *C.char) *C.char {

View File

@ -104,40 +104,6 @@ func SignGroupMembership(content string) string {
return string(data)
}
// EnableInstallation enables an installation for multi-device sync.
func EnableInstallation(installationID string) string {
err := statusBackend.EnableInstallation(installationID)
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return string(data)
}
// DisableInstallation disables an installation for multi-device sync.
func DisableInstallation(installationID string) string {
err := statusBackend.DisableInstallation(installationID)
if err != nil {
return makeJSONResponse(err)
}
data, err := json.Marshal(struct {
Response string `json:"response"`
}{Response: "ok"})
if err != nil {
return makeJSONResponse(err)
}
return string(data)
}
// ValidateNodeConfig validates config for the Status node.
func ValidateNodeConfig(configJSON string) string {
var resp APIDetailedResponse

View File

@ -18,6 +18,7 @@ import (
"github.com/status-im/status-go/db"
"github.com/status-im/status-go/mailserver"
"github.com/status-im/status-go/services/shhext/chat"
"github.com/status-im/status-go/services/shhext/chat/multidevice"
"github.com/status-im/status-go/services/shhext/dedup"
"github.com/status-im/status-go/services/shhext/filter"
"github.com/status-im/status-go/services/shhext/mailservers"
@ -608,6 +609,26 @@ func (api *PublicAPI) RemoveFilters(parent context.Context, chats []*filter.Chat
return api.service.RemoveFilters(chats)
}
// EnableInstallation enables an installation for multi-device sync.
func (api *PublicAPI) EnableInstallation(installationID string) error {
return api.service.EnableInstallation(installationID)
}
// DisableInstallation disables an installation for multi-device sync.
func (api *PublicAPI) DisableInstallation(installationID string) error {
return api.service.DisableInstallation(installationID)
}
// GetOurInstallations returns all the installations available given an identity
func (api *PublicAPI) GetOurInstallations() ([]*multidevice.Installation, error) {
return api.service.GetOurInstallations()
}
// SetInstallationMetadata sets the metadata for our own installation
func (api *PublicAPI) SetInstallationMetadata(installationID string, data *multidevice.InstallationMetadata) error {
return api.service.SetInstallationMetadata(installationID, data)
}
// -----
// HELPER
// -----

View File

@ -15,6 +15,8 @@
// 1559627659_add_contact_code.up.sql
// 1561059285_add_whisper_keys.down.sql
// 1561059285_add_whisper_keys.up.sql
// 1561368210_add_installation_metadata.down.sql
// 1561368210_add_installation_metadata.up.sql
// static.go
// DO NOT EDIT!
@ -358,7 +360,7 @@ func _1561059285_add_whisper_keysDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1561059285_add_whisper_keys.down.sql", size: 25, mode: os.FileMode(420), modTime: time.Unix(1561059394, 0)}
info := bindataFileInfo{name: "1561059285_add_whisper_keys.down.sql", size: 25, mode: os.FileMode(420), modTime: time.Unix(1561361219, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -378,7 +380,47 @@ func _1561059285_add_whisper_keysUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1561059285_add_whisper_keys.up.sql", size: 112, mode: os.FileMode(420), modTime: time.Unix(1561097945, 0)}
info := bindataFileInfo{name: "1561059285_add_whisper_keys.up.sql", size: 112, mode: os.FileMode(420), modTime: time.Unix(1561361219, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1561368210_add_installation_metadataDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x2b\x8e\xcf\x4d\x2d\x49\x4c\x49\x2c\x49\xb4\xe6\x02\x04\x00\x00\xff\xff\x03\x72\x7f\x08\x23\x00\x00\x00")
func _1561368210_add_installation_metadataDownSqlBytes() ([]byte, error) {
return bindataRead(
__1561368210_add_installation_metadataDownSql,
"1561368210_add_installation_metadata.down.sql",
)
}
func _1561368210_add_installation_metadataDownSql() (*asset, error) {
bytes, err := _1561368210_add_installation_metadataDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1561368210_add_installation_metadata.down.sql", size: 35, mode: os.FileMode(420), modTime: time.Unix(1561459323, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __1561368210_add_installation_metadataUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\xce\xc1\x8a\x83\x30\x10\xc6\xf1\xbb\x4f\xf1\xdd\x54\xf0\x0d\xf6\x14\xb3\x23\x08\x21\xd9\x95\x04\x7a\x93\x60\x52\x08\xd5\x58\xe8\x50\xf0\xed\x8b\x87\x42\xed\xc1\xeb\xcc\xef\x83\xbf\x1c\x48\x58\x82\x15\xad\x22\xa4\xfc\x60\x3f\xcf\x9e\xd3\x9a\xc7\x25\xb2\x0f\x9e\x3d\x50\x15\x40\x0a\x31\x73\xe2\x0d\xad\x32\x2d\xb4\xb1\xd0\x4e\xa9\x66\xff\x7c\x8e\x52\x80\xa5\x8b\x3d\x80\xec\x97\x78\xbc\xe2\x97\x3a\xe1\x94\x45\x59\xee\x20\xc4\x67\x9a\xe2\xc8\xdb\xfd\xdc\x5d\xa7\x65\xe4\xf5\x16\xf3\xa9\x72\xba\xff\x77\x54\xbd\x83\x9b\xef\xc0\x1a\x46\x43\x1a\xdd\xa9\x5e\x5a\x0c\xf4\xa7\x84\xa4\xa2\xfe\x29\x5e\x01\x00\x00\xff\xff\x5d\x6f\xe6\xd3\x0b\x01\x00\x00")
func _1561368210_add_installation_metadataUpSqlBytes() ([]byte, error) {
return bindataRead(
__1561368210_add_installation_metadataUpSql,
"1561368210_add_installation_metadata.up.sql",
)
}
func _1561368210_add_installation_metadataUpSql() (*asset, error) {
bytes, err := _1561368210_add_installation_metadataUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1561368210_add_installation_metadata.up.sql", size: 267, mode: os.FileMode(420), modTime: time.Unix(1561459769, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -470,6 +512,8 @@ var _bindata = map[string]func() (*asset, error){
"1559627659_add_contact_code.up.sql": _1559627659_add_contact_codeUpSql,
"1561059285_add_whisper_keys.down.sql": _1561059285_add_whisper_keysDownSql,
"1561059285_add_whisper_keys.up.sql": _1561059285_add_whisper_keysUpSql,
"1561368210_add_installation_metadata.down.sql": _1561368210_add_installation_metadataDownSql,
"1561368210_add_installation_metadata.up.sql": _1561368210_add_installation_metadataUpSql,
"static.go": staticGo,
}
@ -528,6 +572,8 @@ var _bintree = &bintree{nil, map[string]*bintree{
"1559627659_add_contact_code.up.sql": &bintree{_1559627659_add_contact_codeUpSql, map[string]*bintree{}},
"1561059285_add_whisper_keys.down.sql": &bintree{_1561059285_add_whisper_keysDownSql, map[string]*bintree{}},
"1561059285_add_whisper_keys.up.sql": &bintree{_1561059285_add_whisper_keysUpSql, map[string]*bintree{}},
"1561368210_add_installation_metadata.down.sql": &bintree{_1561368210_add_installation_metadataDownSql, map[string]*bintree{}},
"1561368210_add_installation_metadata.up.sql": &bintree{_1561368210_add_installation_metadataUpSql, map[string]*bintree{}},
"static.go": &bintree{staticGo, map[string]*bintree{}},
}}

View File

@ -6,7 +6,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/suite"
"os"
"sort"
"testing"
"github.com/status-im/status-go/services/shhext/chat/multidevice"
@ -69,7 +68,7 @@ func setupUser(user string, s *EncryptionServiceMultiDeviceSuite, n int) error {
DefaultEncryptionServiceConfig(installationID)),
sharedSecretService,
multideviceService,
func(s []*multidevice.IdentityAndID) {},
func(s []*multidevice.Installation) {},
func(s []*sharedsecret.Secret) {},
)
@ -111,12 +110,20 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
// Add alice2 bundle
response, err := s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice2Bundle)
s.Require().NoError(err)
s.Require().Equal(multidevice.IdentityAndID{alice2Identity, "alice2"}, *response[0])
s.Require().Equal(multidevice.Installation{
Identity: alice2Identity,
Version: 1,
ID: "alice2",
}, *response[0])
// Add alice3 bundle
response, err = s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice3Bundle)
s.Require().NoError(err)
s.Require().Equal(multidevice.IdentityAndID{alice3Identity, "alice3"}, *response[0])
s.Require().Equal(multidevice.Installation{
Identity: alice3Identity,
Version: 1,
ID: "alice3",
}, *response[0])
// No installation is enabled
alice1MergedBundle1, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
@ -141,16 +148,6 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice2"])
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice3"])
response, err = s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice1MergedBundle2)
s.Require().NoError(err)
sort.Slice(response, func(i, j int) bool {
return response[i].ID < response[j].ID
})
// We only get back installationIDs not equal to us
s.Require().Equal(2, len(response))
s.Require().Equal(multidevice.IdentityAndID{alice2Identity, "alice2"}, *response[0])
s.Require().Equal(multidevice.IdentityAndID{alice2Identity, "alice3"}, *response[1])
// We disable the installations
err = s.services[aliceUser].services[0].DisableInstallation(&aliceKey.PublicKey, "alice2")
s.Require().NoError(err)

View File

@ -80,7 +80,7 @@ func (s *EncryptionServiceTestSuite) initDatabases(baseConfig *EncryptionService
aliceEncryptionService,
aliceSharedSecretService,
aliceMultideviceService,
func(s []*multidevice.IdentityAndID) {},
func(s []*multidevice.Installation) {},
func(s []*sharedsecret.Secret) {},
)
@ -106,7 +106,7 @@ func (s *EncryptionServiceTestSuite) initDatabases(baseConfig *EncryptionService
bobEncryptionService,
bobSharedSecretService,
bobMultideviceService,
func(s []*multidevice.IdentityAndID) {},
func(s []*multidevice.Installation) {},
func(s []*sharedsecret.Secret) {},
)

View File

@ -8,5 +8,9 @@ type Persistence interface {
// DisableInstallation disable the installation.
DisableInstallation(identity []byte, installationID string) error
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error
AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error)
// GetInstallations returns all the installations for a given identity
GetInstallations(identity []byte) ([]*Installation, error)
// SetInstallationMetadata sets the metadata for a given installation
SetInstallationMetadata(identity []byte, installationID string, data *InstallationMetadata) error
}

View File

@ -7,11 +7,28 @@ import (
"github.com/status-im/status-go/services/shhext/chat/protobuf"
)
type InstallationMetadata struct {
// The name of the device
Name string `json:"name"`
// The type of device
DeviceType string `json:"deviceType"`
// The FCMToken for mobile devices
FCMToken string `json:"fcmToken"`
}
type Installation struct {
// Identity is the string identity of the owner
Identity string `json:"identity"`
// The installation-id of the device
ID string
ID string `json:"id"`
// The last known protocol version of the device
Version uint32
Version uint32 `json:"version"`
// Enabled is whether the installation is enabled
Enabled bool `json:"enabled"`
// Timestamp is the last time we saw this device
Timestamp int64 `json:"timestamp"`
// InstallationMetadata
InstallationMetadata *InstallationMetadata `json:"metadata"`
}
type Config struct {
@ -32,11 +49,6 @@ type Service struct {
config *Config
}
type IdentityAndID struct {
Identity string
ID string
}
func (s *Service) GetActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
identityC := crypto.CompressPubkey(identity)
return s.persistence.GetActiveInstallations(s.config.MaxInstallations, identityC)
@ -57,6 +69,38 @@ func (s *Service) GetOurActiveInstallations(identity *ecdsa.PublicKey) ([]*Insta
return installations, nil
}
func (s *Service) GetOurInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
var found bool
identityC := crypto.CompressPubkey(identity)
installations, err := s.persistence.GetInstallations(identityC)
if err != nil {
return nil, err
}
for _, installation := range installations {
if installation.ID == s.config.InstallationID {
found = true
installation.Enabled = true
installation.Version = s.config.ProtocolVersion
}
}
if !found {
installations = append(installations, &Installation{
ID: s.config.InstallationID,
Enabled: true,
Version: s.config.ProtocolVersion,
})
}
return installations, nil
}
func (s *Service) SetInstallationMetadata(identity *ecdsa.PublicKey, installationID string, metadata *InstallationMetadata) error {
identityC := crypto.CompressPubkey(identity)
return s.persistence.SetInstallationMetadata(identityC, installationID, metadata)
}
func (s *Service) EnableInstallation(identity *ecdsa.PublicKey, installationID string) error {
identityC := crypto.CompressPubkey(identity)
return s.persistence.EnableInstallation(identityC, installationID)
@ -68,9 +112,8 @@ func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installati
}
// ProcessPublicBundle persists a bundle and returns a list of tuples identity/installationID
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, theirIdentity *ecdsa.PublicKey, b *protobuf.Bundle) ([]*IdentityAndID, error) {
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, theirIdentity *ecdsa.PublicKey, b *protobuf.Bundle) ([]*Installation, error) {
signedPreKeys := b.GetSignedPreKeys()
var response []*IdentityAndID
var installations []*Installation
myIdentityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(&myIdentityKey.PublicKey))
@ -83,16 +126,12 @@ func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, theirIden
for installationID, signedPreKey := range signedPreKeys {
if installationID != s.config.InstallationID {
installations = append(installations, &Installation{
Identity: theirIdentityStr,
ID: installationID,
Version: signedPreKey.GetProtocolVersion(),
})
response = append(response, &IdentityAndID{theirIdentityStr, installationID})
}
}
if err := s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installations, fromOurIdentity); err != nil {
return nil, err
}
return response, nil
return s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installations, fromOurIdentity)
}

View File

@ -32,8 +32,10 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi
}
for rows.Next() {
var installationID string
var version uint32
var (
installationID string
version uint32
)
err = rows.Scan(
&installationID,
&version,
@ -44,6 +46,7 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi
installations = append(installations, &Installation{
ID: installationID,
Version: version,
Enabled: true,
})
}
@ -52,20 +55,116 @@ func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identi
}
// GetInstallations returns all the installations for a given identity
// we both return the installations & the metadata
// metadata is currently stored in a separate table, as in some cases we
// might have metadata for a device, but no other information on the device
func (s *SQLLitePersistence) GetInstallations(identity []byte) ([]*Installation, error) {
installationMap := make(map[string]*Installation)
var installations []*Installation
// We query both tables as sqlite does not support full outer joins
installationsStmt, err := s.db.Prepare(`SELECT installation_id, version, enabled, timestamp FROM installations WHERE identity = ?`)
if err != nil {
return nil, err
}
defer installationsStmt.Close()
installationRows, err := installationsStmt.Query(identity)
if err != nil {
return nil, err
}
for installationRows.Next() {
var (
installationID string
version uint32
enabled bool
timestamp int64
)
err = installationRows.Scan(
&installationID,
&version,
&enabled,
&timestamp,
)
if err != nil {
return nil, err
}
installationMap[installationID] = &Installation{
ID: installationID,
Version: version,
Enabled: enabled,
Timestamp: timestamp,
InstallationMetadata: &InstallationMetadata{},
}
}
metadataStmt, err := s.db.Prepare(`SELECT installation_id, name, device_type, fcm_token FROM installation_metadata WHERE identity = ?`)
if err != nil {
return nil, err
}
defer metadataStmt.Close()
metadataRows, err := metadataStmt.Query(identity)
if err != nil {
return nil, err
}
for metadataRows.Next() {
var (
installationID string
name sql.NullString
deviceType sql.NullString
fcmToken sql.NullString
installation *Installation
)
err = metadataRows.Scan(
&installationID,
&name,
&deviceType,
&fcmToken,
)
if err != nil {
return nil, err
}
if _, ok := installationMap[installationID]; ok {
installation = installationMap[installationID]
} else {
installation = &Installation{ID: installationID}
}
installation.InstallationMetadata = &InstallationMetadata{
Name: name.String,
DeviceType: deviceType.String,
FCMToken: fcmToken.String,
}
installationMap[installationID] = installation
}
for _, installation := range installationMap {
installations = append(installations, installation)
}
return installations, nil
}
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error {
func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) {
tx, err := s.db.Begin()
if err != nil {
return nil
return nil, err
}
var insertedInstallations []*Installation
for _, installation := range installations {
stmt, err := tx.Prepare(`SELECT enabled, version
FROM installations
WHERE identity = ? AND installation_id = ?
LIMIT 1`)
if err != nil {
return err
return nil, err
}
defer stmt.Close()
@ -76,14 +175,14 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
if err != nil && err != sql.ErrNoRows {
return err
return nil, err
}
if err == sql.ErrNoRows {
stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
VALUES (?, ?, ?, ?, ?)`)
if err != nil {
return err
return nil, err
}
defer stmt.Close()
@ -95,8 +194,9 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
latestVersion,
)
if err != nil {
return err
return nil, err
}
insertedInstallations = append(insertedInstallations, installation)
} else {
// We update timestamp if present without changing enabled, only if this is a new bundle
// and we set the version to the latest we ever saw
@ -110,7 +210,7 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
AND installation_id = ?
AND timestamp < ?`)
if err != nil {
return err
return nil, err
}
defer stmt.Close()
@ -123,7 +223,7 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
timestamp,
)
if err != nil {
return err
return nil, err
}
}
@ -131,10 +231,10 @@ func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64,
if err := tx.Commit(); err != nil {
_ = tx.Rollback()
return err
return nil, err
}
return nil
return insertedInstallations, nil
}
@ -165,3 +265,14 @@ func (s *SQLLitePersistence) DisableInstallation(identity []byte, installationID
_, err = stmt.Exec(identity, installationID)
return err
}
// SetInstallationMetadata sets the metadata for a given installation
func (s *SQLLitePersistence) SetInstallationMetadata(identity []byte, installationID string, metadata *InstallationMetadata) error {
stmt, err := s.db.Prepare(`INSERT INTO installation_metadata(name, device_type, fcm_token, identity, installation_id) VALUES(?,?,?,?,?)`)
if err != nil {
return err
}
_, err = stmt.Exec(metadata.Name, metadata.DeviceType, metadata.FCMToken, identity, installationID)
return err
}

View File

@ -36,30 +36,30 @@ func (s *SQLLitePersistenceTestSuite) SetupTest() {
func (s *SQLLitePersistenceTestSuite) TestAddInstallations() {
identity := []byte("alice")
installations := []*Installation{
{ID: "alice-1", Version: 1},
{ID: "alice-2", Version: 2},
{ID: "alice-1", Version: 1, Enabled: true},
{ID: "alice-2", Version: 2, Enabled: true},
}
err := s.service.AddInstallations(
addedInstallations, err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
s.Require().NoError(err)
s.Require().Equal(installations, enabledInstallations)
s.Require().Equal(installations, addedInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() {
identity := []byte("alice")
installations := []*Installation{
{ID: "alice-1", Version: 1},
{ID: "alice-1", Version: 1, Enabled: true},
}
err := s.service.AddInstallations(
_, err := s.service.AddInstallations(
identity,
1,
installations,
@ -77,7 +77,7 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() {
{ID: "alice-1", Version: 0},
}
err = s.service.AddInstallations(
_, err = s.service.AddInstallations(
identity,
3,
installationsWithDowngradedVersion,
@ -98,7 +98,7 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
{ID: "alice-2", Version: 2},
}
err := s.service.AddInstallations(
_, err := s.service.AddInstallations(
identity,
1,
installations,
@ -111,7 +111,7 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
{ID: "alice-3", Version: 3},
}
err = s.service.AddInstallations(
_, err = s.service.AddInstallations(
identity,
2,
installations,
@ -120,12 +120,12 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
s.Require().NoError(err)
installations = []*Installation{
{ID: "alice-2", Version: 2},
{ID: "alice-3", Version: 3},
{ID: "alice-4", Version: 4},
{ID: "alice-2", Version: 2, Enabled: true},
{ID: "alice-3", Version: 3, Enabled: true},
{ID: "alice-4", Version: 4, Enabled: true},
}
err = s.service.AddInstallations(
_, err = s.service.AddInstallations(
identity,
3,
installations,
@ -147,7 +147,7 @@ func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() {
{ID: "alice-2", Version: 2},
}
err := s.service.AddInstallations(
_, err := s.service.AddInstallations(
identity,
1,
installations,
@ -169,7 +169,7 @@ func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
{ID: "alice-2", Version: 2},
}
err := s.service.AddInstallations(
_, err := s.service.AddInstallations(
identity,
1,
installations,
@ -186,18 +186,19 @@ func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
{ID: "alice-2", Version: 2},
}
err = s.service.AddInstallations(
addedInstallations, err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
s.Require().Equal(0, len(addedInstallations))
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
expected := []*Installation{{ID: "alice-2", Version: 2}}
expected := []*Installation{{ID: "alice-2", Version: 2, Enabled: true}}
s.Require().Equal(expected, actualInstallations)
}
@ -209,7 +210,7 @@ func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
{ID: "alice-2", Version: 2},
}
err := s.service.AddInstallations(
_, err := s.service.AddInstallations(
identity,
1,
installations,
@ -223,7 +224,7 @@ func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
s.Require().NoError(err)
expected := []*Installation{{ID: "alice-2", Version: 2}}
expected := []*Installation{{ID: "alice-2", Version: 2, Enabled: true}}
s.Require().Equal(expected, actualInstallations)
err = s.service.EnableInstallation(identity, "alice-1")
@ -233,9 +234,79 @@ func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
s.Require().NoError(err)
expected = []*Installation{
{ID: "alice-1", Version: 1, Enabled: true},
{ID: "alice-2", Version: 2, Enabled: true},
}
s.Require().Equal(expected, actualInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestGetInstallations() {
identity := []byte("alice")
installations := []*Installation{
{ID: "alice-1", Version: 1},
{ID: "alice-2", Version: 2},
}
s.Require().Equal(expected, actualInstallations)
_, err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
err = s.service.DisableInstallation(identity, "alice-1")
s.Require().NoError(err)
actualInstallations, err := s.service.GetInstallations(identity)
s.Require().NoError(err)
emptyMetadata := &InstallationMetadata{}
expected := []*Installation{
{ID: "alice-1", Version: 1, Timestamp: 1, Enabled: false, InstallationMetadata: emptyMetadata},
{ID: "alice-2", Version: 2, Timestamp: 1, Enabled: true, InstallationMetadata: emptyMetadata},
}
s.Require().Equal(2, len(actualInstallations))
s.Require().ElementsMatch(expected, actualInstallations)
}
func (s *SQLLitePersistenceTestSuite) TestSetMetadata() {
identity := []byte("alice")
installations := []*Installation{
{ID: "alice-1", Version: 1},
{ID: "alice-2", Version: 2},
}
_, err := s.service.AddInstallations(
identity,
1,
installations,
true,
)
s.Require().NoError(err)
err = s.service.DisableInstallation(identity, "alice-1")
s.Require().NoError(err)
emptyMetadata := &InstallationMetadata{}
setMetadata := &InstallationMetadata{
Name: "a",
FCMToken: "b",
DeviceType: "c",
}
err = s.service.SetInstallationMetadata(identity, "alice-2", setMetadata)
s.Require().NoError(err)
actualInstallations, err := s.service.GetInstallations(identity)
s.Require().NoError(err)
expected := []*Installation{
{ID: "alice-1", Version: 1, Timestamp: 1, Enabled: false, InstallationMetadata: emptyMetadata},
{ID: "alice-2", Version: 2, Timestamp: 1, Enabled: true, InstallationMetadata: setMetadata},
}
s.Require().ElementsMatch(expected, actualInstallations)
}

View File

@ -27,7 +27,7 @@ type ProtocolService struct {
encryption *EncryptionService
secret *sharedsecret.Service
multidevice *multidevice.Service
addedBundlesHandler func([]*multidevice.IdentityAndID)
addedBundlesHandler func([]*multidevice.Installation)
onNewSharedSecretHandler func([]*sharedsecret.Secret)
Enabled bool
}
@ -35,7 +35,7 @@ type ProtocolService struct {
var ErrNotProtocolMessage = errors.New("Not a protocol message")
// NewProtocolService creates a new ProtocolService instance
func NewProtocolService(encryption *EncryptionService, secret *sharedsecret.Service, multidevice *multidevice.Service, addedBundlesHandler func([]*multidevice.IdentityAndID), onNewSharedSecretHandler func([]*sharedsecret.Secret)) *ProtocolService {
func NewProtocolService(encryption *EncryptionService, secret *sharedsecret.Service, multidevice *multidevice.Service, addedBundlesHandler func([]*multidevice.Installation), onNewSharedSecretHandler func([]*sharedsecret.Secret)) *ProtocolService {
return &ProtocolService{
log: log.New("package", "status-go/services/sshext.chat"),
encryption: encryption,
@ -193,7 +193,7 @@ func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destin
}
// ProcessPublicBundle processes a received X3DH bundle.
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]*multidevice.IdentityAndID, error) {
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]*multidevice.Installation, error) {
if err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle); err != nil {
return nil, err
}
@ -227,6 +227,16 @@ func (p *ProtocolService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, in
return p.multidevice.DisableInstallation(myIdentityKey, installationID)
}
// GetOurInstallations returns all the installations available given an identity
func (p *ProtocolService) GetOurInstallations(myIdentityKey *ecdsa.PublicKey) ([]*multidevice.Installation, error) {
return p.multidevice.GetOurInstallations(myIdentityKey)
}
// SetInstallationMetadata sets the metadata for our own installation
func (p *ProtocolService) SetInstallationMetadata(myIdentityKey *ecdsa.PublicKey, installationID string, data *multidevice.InstallationMetadata) error {
return p.multidevice.SetInstallationMetadata(myIdentityKey, installationID, data)
}
// GetPublicBundle retrieves a public bundle given an identity
func (p *ProtocolService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*protobuf.Bundle, error) {
installations, err := p.multidevice.GetActiveInstallations(theirIdentityKey)

View File

@ -39,7 +39,7 @@ func (s *ProtocolServiceTestSuite) SetupTest() {
panic(err)
}
addedBundlesHandler := func(addedBundles []*multidevice.IdentityAndID) {}
addedBundlesHandler := func(addedBundles []*multidevice.Installation) {}
onNewSharedSecretHandler := func(secret []*sharedsecret.Secret) {}
aliceMultideviceConfig := &multidevice.Config{

View File

@ -37,6 +37,7 @@ var (
// ErrPFSNotEnabled is returned when an endpoint PFS only is called but
// PFS is disabled
ErrPFSNotEnabled = errors.New("pfs not enabled")
errNoKeySelected = errors.New("no key selected")
)
type Service struct {
@ -134,7 +135,7 @@ func (s *Service) initProtocol(address, encKey, password string) error {
return err
}
addedBundlesHandler := func(addedBundles []*multidevice.IdentityAndID) {
addedBundlesHandler := func(addedBundles []*multidevice.Installation) {
handler := SignalHandler{}
for _, bundle := range addedBundles {
handler.BundleAdded(bundle.Identity, bundle.ID)
@ -142,7 +143,6 @@ func (s *Service) initProtocol(address, encKey, password string) error {
}
// Initialize persistence
s.persistence = NewSQLLitePersistence(persistence.DB)
// Initialize sharedsecret
@ -171,7 +171,7 @@ func (s *Service) initProtocol(address, encKey, password string) error {
return nil
}
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]*multidevice.IdentityAndID, error) {
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]*multidevice.Installation, error) {
if s.protocol == nil {
return nil, errProtocolNotInitialized
}
@ -188,12 +188,79 @@ func (s *Service) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*protobuf.Bundle,
}
// EnableInstallation enables an installation for multi-device sync.
func (s *Service) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
func (s *Service) EnableInstallation(installationID string) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
return s.protocol.EnableInstallation(myIdentityKey, installationID)
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
if err != nil {
return err
}
return s.protocol.EnableInstallation(&privateKey.PublicKey, installationID)
}
// DisableInstallation disables an installation for multi-device sync.
func (s *Service) DisableInstallation(installationID string) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
if err != nil {
return err
}
return s.protocol.DisableInstallation(&privateKey.PublicKey, installationID)
}
// GetOurInstallations returns all the installations available given an identity
func (s *Service) GetOurInstallations() ([]*multidevice.Installation, error) {
if s.protocol == nil {
return nil, errProtocolNotInitialized
}
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return nil, errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
if err != nil {
return nil, err
}
return s.protocol.GetOurInstallations(&privateKey.PublicKey)
}
// SetInstallationMetadata sets the metadata for our own installation
func (s *Service) SetInstallationMetadata(installationID string, data *multidevice.InstallationMetadata) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
if err != nil {
return err
}
return s.protocol.SetInstallationMetadata(&privateKey.PublicKey, installationID, data)
}
func (s *Service) GetPublicBundle(identityKey *ecdsa.PublicKey) (*protobuf.Bundle, error) {
@ -204,15 +271,6 @@ func (s *Service) GetPublicBundle(identityKey *ecdsa.PublicKey) (*protobuf.Bundl
return s.protocol.GetPublicBundle(identityKey)
}
// DisableInstallation disables an installation for multi-device sync.
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
if s.protocol == nil {
return errProtocolNotInitialized
}
return s.protocol.DisableInstallation(myIdentityKey, installationID)
}
func (s *Service) Start(online func() bool, startTicker bool) error {
s.online = online
if startTicker {
@ -283,7 +341,7 @@ func (s *Service) ProcessMessage(dedupMessage dedup.DeduplicateMessage) error {
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return errors.New("no key selected")
return errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
@ -425,7 +483,7 @@ func (s *Service) CreatePublicMessage(signature string, chatID string, payload [
if wrap {
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return nil, errors.New("no key selected")
return nil, errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
@ -501,7 +559,7 @@ func (s *Service) sendContactCode() (*whisper.NewMessage, error) {
privateKeyID := s.whisper.SelectedKeyPairID()
if privateKeyID == "" {
return nil, errors.New("no key selected")
return nil, errNoKeySelected
}
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)

View File

@ -0,0 +1 @@
DROP TABLE installations_metadata;

View File

@ -0,0 +1,8 @@
CREATE TABLE installation_metadata (
identity BLOB NOT NULL,
installation_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
device_type TEXT NOT NULL DEFAULT '',
fcm_token TEXT NOT NULL DEFAULT '',
UNIQUE(identity, installation_id) ON CONFLICT REPLACE
);