support local pairing after logged in as receiver; pair installation;(#3202)

This commit is contained in:
frank 2023-02-28 20:32:45 +08:00 committed by GitHub
parent 411607d43d
commit 837bf2ca42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 802 additions and 306 deletions

View File

@ -1 +1 @@
0.133.3 0.134.0

View File

@ -215,6 +215,11 @@ func (b *GethStatusBackend) DeleteMultiaccount(keyUID string, keyStoreDir string
} }
} }
if b.account != nil && b.account.KeyUID == keyUID {
// reset active account
b.account = nil
}
return os.RemoveAll(keyStoreDir) return os.RemoveAll(keyStoreDir)
} }
@ -1362,6 +1367,7 @@ func (b *GethStatusBackend) Logout() error {
b.AccountManager().Logout() b.AccountManager().Logout()
b.appDB = nil b.appDB = nil
b.account = nil
if b.statusNode != nil { if b.statusNode != nil {
if err := b.statusNode.Stop(); err != nil { if err := b.statusNode.Stop(); err != nil {

View File

@ -208,7 +208,7 @@ func (db *Database) GetAccounts() (rst []Account, err error) {
} }
func (db *Database) GetAccount(keyUID string) (*Account, error) { func (db *Database) GetAccount(keyUID string) (*Account, error) {
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.keycardPairing, a.keyUid, ii.key_uid, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid WHERE a.keyUid = ? ORDER BY loginTimestamp DESC", keyUID) rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.keycardPairing, a.keyUid, a.kdfIterations, ii.key_uid, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid WHERE a.keyUid = ? ORDER BY loginTimestamp DESC", keyUID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -241,6 +241,7 @@ func (db *Database) GetAccount(keyUID string) (*Account, error) {
&accColorID, &accColorID,
&acc.KeycardPairing, &acc.KeycardPairing,
&acc.KeyUID, &acc.KeyUID,
&acc.KDFIterations,
&iiKeyUID, &iiKeyUID,
&iiName, &iiName,
&ii.Payload, &ii.Payload,

View File

@ -182,7 +182,7 @@ func TestDatabase_GetAccount(t *testing.T) {
db, stop := setupTestDB(t) db, stop := setupTestDB(t)
defer stop() defer stop()
expected := Account{Name: "string", KeyUID: keyUID, ColorHash: ColourHash{{4, 3}, {4, 0}, {4, 3}, {4, 0}}, ColorID: 10} expected := Account{Name: "string", KeyUID: keyUID, ColorHash: ColourHash{{4, 3}, {4, 0}, {4, 3}, {4, 0}}, ColorID: 10, KDFIterations: sqlite.ReducedKDFIterationsNumber}
require.NoError(t, db.SaveAccount(expected)) require.NoError(t, db.SaveAccount(expected))
account, err := db.GetAccount(expected.KeyUID) account, err := db.GetAccount(expected.KeyUID)

View File

@ -2384,7 +2384,7 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity_RequestToJoin() {
func (s *MessengerCommunitiesSuite) pairTwoDevices(device1, device2 *Messenger, deviceName, deviceType string) { func (s *MessengerCommunitiesSuite) pairTwoDevices(device1, device2 *Messenger, deviceName, deviceType string) {
// Send pairing data // Send pairing data
response, err := device1.SendPairInstallation(context.Background()) response, err := device1.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Len(response.Chats(), 1) s.Len(response.Chats(), 1)

View File

@ -401,6 +401,10 @@ func (p *Protocol) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *
return p.multidevice.AddInstallations(bundle.GetIdentity(), bundle.GetTimestamp(), installations, enabled) return p.multidevice.AddInstallations(bundle.GetIdentity(), bundle.GetTimestamp(), installations, enabled)
} }
func (p *Protocol) GetMultiDevice() *multidevice.Multidevice {
return p.multidevice
}
// recoverInstallationsFromBundle extracts installations from the bundle. // recoverInstallationsFromBundle extracts installations from the bundle.
// It returns extracted installations and true if the installations // It returns extracted installations and true if the installations
// are ours, i.e. the bundle was created by our identity key. // are ours, i.e. the bundle was created by our identity key.

View File

@ -750,11 +750,6 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
return nil, err return nil, err
} }
err = m.setInstallationHostname()
if err != nil {
return nil, err
}
return response, nil return response, nil
} }
@ -762,6 +757,10 @@ func (m *Messenger) IdentityPublicKey() *ecdsa.PublicKey {
return &m.identity.PublicKey return &m.identity.PublicKey
} }
func (m *Messenger) IdentityPublicKeyCompressed() []byte {
return crypto.CompressPubkey(m.IdentityPublicKey())
}
// cleanTopics remove any topic that does not have a Listen flag set // cleanTopics remove any topic that does not have a Listen flag set
func (m *Messenger) cleanTopics() error { func (m *Messenger) cleanTopics() error {
if m.mailserversDatabase == nil { if m.mailserversDatabase == nil {
@ -1572,6 +1571,11 @@ func (m *Messenger) Init() error {
m.allInstallations.Store(installation.ID, installation) m.allInstallations.Store(installation.ID, installation)
} }
err = m.setInstallationHostname()
if err != nil {
return err
}
_, err = m.transport.InitFilters(publicChatIDs, publicKeys) _, err = m.transport.InitFilters(publicChatIDs, publicKeys)
return err return err
} }
@ -1767,6 +1771,10 @@ func (m *Messenger) hasPairedDevices() bool {
return count > 1 return count > 1
} }
func (m *Messenger) HasPairedDevices() bool {
return m.hasPairedDevices()
}
// sendToPairedDevices will check if we have any paired devices and send to them if necessary // sendToPairedDevices will check if we have any paired devices and send to them if necessary
func (m *Messenger) sendToPairedDevices(ctx context.Context, spec common.RawMessage) error { func (m *Messenger) sendToPairedDevices(ctx context.Context, spec common.RawMessage) error {
hasPairedDevices := m.hasPairedDevices() hasPairedDevices := m.hasPairedDevices()
@ -1780,23 +1788,23 @@ func (m *Messenger) sendToPairedDevices(ctx context.Context, spec common.RawMess
return nil return nil
} }
func (m *Messenger) dispatchPairInstallationMessage(ctx context.Context, spec common.RawMessage) ([]byte, error) { func (m *Messenger) dispatchPairInstallationMessage(ctx context.Context, spec common.RawMessage) (common.RawMessage, error) {
var err error var err error
var id []byte var id []byte
id, err = m.sender.SendPairInstallation(ctx, &m.identity.PublicKey, spec) id, err = m.sender.SendPairInstallation(ctx, &m.identity.PublicKey, spec)
if err != nil { if err != nil {
return nil, err return spec, err
} }
spec.ID = types.EncodeHex(id) spec.ID = types.EncodeHex(id)
spec.SendCount++ spec.SendCount++
err = m.persistence.SaveRawMessage(&spec) err = m.persistence.SaveRawMessage(&spec)
if err != nil { if err != nil {
return nil, err return spec, err
} }
return id, nil return spec, nil
} }
func (m *Messenger) dispatchMessage(ctx context.Context, rawMessage common.RawMessage) (common.RawMessage, error) { func (m *Messenger) dispatchMessage(ctx context.Context, rawMessage common.RawMessage) (common.RawMessage, error) {
@ -2505,7 +2513,7 @@ func (m *Messenger) getLastClockWithRelatedChat() (uint64, *Chat) {
} }
// SendPairInstallation sends a pair installation message // SendPairInstallation sends a pair installation message
func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerResponse, error) { func (m *Messenger) SendPairInstallation(ctx context.Context, rawMessageHandler RawMessageHandler) (*MessengerResponse, error) {
var err error var err error
var response MessengerResponse var response MessengerResponse
@ -2524,13 +2532,17 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
Clock: clock, Clock: clock,
Name: installation.InstallationMetadata.Name, Name: installation.InstallationMetadata.Name,
InstallationId: installation.ID, InstallationId: installation.ID,
DeviceType: installation.InstallationMetadata.DeviceType} DeviceType: installation.InstallationMetadata.DeviceType,
Version: installation.Version}
encodedMessage, err := proto.Marshal(pairMessage) encodedMessage, err := proto.Marshal(pairMessage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = m.dispatchPairInstallationMessage(ctx, common.RawMessage{ if rawMessageHandler == nil {
rawMessageHandler = m.dispatchPairInstallationMessage
}
_, err = rawMessageHandler(ctx, common.RawMessage{
LocalChatID: chat.ID, LocalChatID: chat.ID,
Payload: encodedMessage, Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION, MessageType: protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION,

View File

@ -84,7 +84,7 @@ func (s *MessengerDeleteMessageForMeSuite) Pair() {
DeviceType: "alice2", DeviceType: "alice2",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.alice2.SendPairInstallation(context.Background()) response, err := s.alice2.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -153,17 +153,9 @@ func (m *Messenger) SetSocialLinks(socialLinks *identity.SocialLinks) error {
} }
func (m *Messenger) setInstallationHostname() error { func (m *Messenger) setInstallationHostname() error {
ourInstallation, ok := m.allInstallations.Load(m.installationID) imd, err := m.getOurInstallationMetadata()
if !ok { if err != nil {
m.logger.Error("Messenger's installationID is not set or not loadable") return err
return nil
}
var imd *multidevice.InstallationMetadata
if ourInstallation.InstallationMetadata == nil {
imd = new(multidevice.InstallationMetadata)
} else {
imd = ourInstallation.InstallationMetadata
} }
// If the name is already set, don't do anything // If the name is already set, don't do anything
@ -178,3 +170,35 @@ func (m *Messenger) setInstallationHostname() error {
imd.Name = fmt.Sprintf("%s %s", hn, imd.Name) imd.Name = fmt.Sprintf("%s %s", hn, imd.Name)
return m.setInstallationMetadata(m.installationID, imd) return m.setInstallationMetadata(m.installationID, imd)
} }
func (m *Messenger) getOurInstallationMetadata() (*multidevice.InstallationMetadata, error) {
ourInstallation, ok := m.allInstallations.Load(m.installationID)
if !ok {
return nil, fmt.Errorf("messenger's installationID is not set or not loadable")
}
if ourInstallation.InstallationMetadata == nil {
return new(multidevice.InstallationMetadata), nil
}
return ourInstallation.InstallationMetadata, nil
}
func (m *Messenger) SetInstallationDeviceType(deviceType string) error {
if strings.TrimSpace(deviceType) == "" {
return errors.New("device type is empty")
}
imd, err := m.getOurInstallationMetadata()
if err != nil {
return err
}
// If the name is already set, don't do anything
if len(imd.DeviceType) != 0 {
return nil
}
imd.DeviceType = deviceType
return m.setInstallationMetadata(m.installationID, imd)
}

View File

@ -78,7 +78,7 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)
@ -217,7 +217,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)
@ -311,7 +311,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := bob2.SendPairInstallation(context.Background()) response, err := bob2.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -85,7 +85,7 @@ func (s *MessengerSyncBookmarkSuite) TestSyncBookmark() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -86,7 +86,7 @@ func (s *MessengerSyncChatSuite) Pair() {
DeviceType: "alice2", DeviceType: "alice2",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.alice2.SendPairInstallation(context.Background()) response, err := s.alice2.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -76,7 +76,7 @@ func (s *MessengerSyncClearHistory) pair() *Messenger {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)

View File

@ -59,7 +59,7 @@ func (s *MessengerSyncKeycardChangeSuite) SetupTest() {
} }
err = s.other.SetInstallationMetadata(s.other.installationID, imOther) err = s.other.SetInstallationMetadata(s.other.installationID, imOther)
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.other.SendPairInstallation(context.Background()) response, err := s.other.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)

View File

@ -60,7 +60,7 @@ func (s *MessengerSyncKeycardsStateSuite) SetupTest() {
} }
err = s.other.SetInstallationMetadata(s.other.installationID, imOther) err = s.other.SetInstallationMetadata(s.other.installationID, imOther)
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.other.SendPairInstallation(context.Background()) response, err := s.other.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)

View File

@ -80,7 +80,7 @@ func (s *MessengerSyncProfilePictureSuite) TestSyncProfilePicture() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -2,16 +2,16 @@ package protocol
import ( import (
"context" "context"
"errors"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
localnotifications "github.com/status-im/status-go/services/local-notifications" localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
"github.com/status-im/status-go/protocol/protobuf"
) )
type RawMessageHandler func(ctx context.Context, rawMessage common.RawMessage) (common.RawMessage, error) type RawMessageHandler func(ctx context.Context, rawMessage common.RawMessage) (common.RawMessage, error)
@ -199,6 +199,47 @@ func (m *Messenger) HandleSyncRawMessages(rawMessages []*protobuf.RawMessage) er
m.logger.Error("failed to handleSyncKeycards when HandleSyncRawMessages", zap.Error(err)) m.logger.Error("failed to handleSyncKeycards when HandleSyncRawMessages", zap.Error(err))
continue continue
} }
case protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION:
var message protobuf.PairInstallation
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
identity := m.myHexIdentity()
installations := []*multidevice.Installation{
{
Identity: identity,
ID: message.InstallationId,
Version: message.Version,
Enabled: true,
Timestamp: int64(message.Clock),
InstallationMetadata: &multidevice.InstallationMetadata{
DeviceType: message.DeviceType,
Name: message.Name,
},
}}
m.handleInstallations(installations)
// set WhisperTimestamp to pass the validation in HandlePairInstallation
state.CurrentMessageState = &CurrentMessageState{WhisperTimestamp: message.Clock}
err = m.HandlePairInstallation(state, message)
if err != nil {
return err
}
multidevice := m.encryptor.GetMultiDevice()
if multidevice == nil {
return errors.New("multidevice is nil")
}
_, err = multidevice.AddInstallations(m.IdentityPublicKeyCompressed(), int64(message.GetClock()), installations, true)
if err != nil {
return err
}
// if receiver already logged in before local pairing, we need force enable the installation,
// AddInstallations won't make sure enable it, e.g. installation maybe already exist in db but not enabled yet
err = m.EnableInstallation(message.InstallationId)
if err != nil {
return err
}
} }
} }
response, err := m.saveDataAndPrepareResponse(state) response, err := m.saveDataAndPrepareResponse(state)

View File

@ -62,7 +62,7 @@ func (s *MessengerSyncSavedAddressesSuite) SetupTest() {
} }
err = s.other.SetInstallationMetadata(s.other.installationID, imOther) err = s.other.SetInstallationMetadata(s.other.installationID, imOther)
s.Require().NoError(err) s.Require().NoError(err)
response, err := s.other.SendPairInstallation(context.Background()) response, err := s.other.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)

View File

@ -182,7 +182,7 @@ func (s *MessengerSyncSettingsSuite) newMessenger() *Messenger {
func pairTwoDevices(s *suite.Suite, device1, device2 *Messenger) { func pairTwoDevices(s *suite.Suite, device1, device2 *Messenger) {
// Send pairing data // Send pairing data
response, err := device1.SendPairInstallation(context.Background()) response, err := device1.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Len(response.Chats(), 1) s.Len(response.Chats(), 1)

View File

@ -72,7 +72,7 @@ func (s *MessengerSyncVerificationRequests) TestSyncVerificationRequests() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)
@ -136,7 +136,7 @@ func (s *MessengerSyncVerificationRequests) TestSyncTrust() {
DeviceType: "their-device-type", DeviceType: "their-device-type",
}) })
s.Require().NoError(err) s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background()) response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -116,7 +116,7 @@ func (s *MessengerSyncWalletSuite) TestSyncWallets() {
} }
err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1) err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1)
s.Require().NoError(err) s.Require().NoError(err)
response, err := alicesOtherDevice.SendPairInstallation(context.Background()) response, err := alicesOtherDevice.SendPairInstallation(context.Background(), nil)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1) s.Require().Len(response.Chats(), 1)

View File

@ -647,6 +647,8 @@ type PairInstallation struct {
InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"` InstallationId string `protobuf:"bytes,2,opt,name=installation_id,json=installationId,proto3" json:"installation_id,omitempty"`
DeviceType string `protobuf:"bytes,3,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"` DeviceType string `protobuf:"bytes,3,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
// following fields used for local pairing
Version uint32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -705,6 +707,13 @@ func (m *PairInstallation) GetName() string {
return "" return ""
} }
func (m *PairInstallation) GetVersion() uint32 {
if m != nil {
return m.Version
}
return 0
}
type SyncInstallationContact struct { type SyncInstallationContact struct {
Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"` Clock uint64 `protobuf:"varint,1,opt,name=clock,proto3" json:"clock,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
@ -2759,168 +2768,168 @@ func init() {
} }
var fileDescriptor_d61ab7221f0b5518 = []byte{ var fileDescriptor_d61ab7221f0b5518 = []byte{
// 2596 bytes of a gzipped FileDescriptorProto // 2602 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x39, 0x4d, 0x73, 0x1b, 0xc7, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x39, 0x4b, 0x73, 0x1b, 0xc7,
0xb1, 0x5e, 0x00, 0xc4, 0x47, 0x03, 0x04, 0xa1, 0x11, 0x2d, 0x41, 0x14, 0x5d, 0x92, 0xd6, 0x76, 0xd1, 0x5e, 0x00, 0xc2, 0xa3, 0xf1, 0x20, 0x34, 0xa2, 0x25, 0x88, 0xa2, 0x4b, 0xd2, 0xda, 0x2e,
0x59, 0xaf, 0xca, 0x8f, 0x7e, 0x4f, 0x4e, 0xe2, 0xc4, 0x1f, 0x65, 0x43, 0x00, 0x62, 0xd1, 0x14, 0xeb, 0xab, 0xf2, 0x47, 0x27, 0x72, 0x12, 0x27, 0x7e, 0x94, 0x0d, 0x01, 0x88, 0x05, 0x53, 0x04,
0x41, 0xd6, 0x90, 0x90, 0x63, 0x57, 0xaa, 0xb6, 0x86, 0xbb, 0x23, 0x62, 0xc3, 0xc5, 0xee, 0x66, 0x59, 0x43, 0x42, 0x8e, 0x5d, 0xa9, 0xda, 0x1a, 0xee, 0x8e, 0x88, 0x0d, 0x17, 0xbb, 0x9b, 0x9d,
0x67, 0x40, 0x05, 0xb9, 0xe5, 0x92, 0x1f, 0x90, 0x4b, 0x72, 0xf4, 0x39, 0xb9, 0xa5, 0xca, 0x39, 0x01, 0x15, 0xe4, 0x96, 0xbf, 0x90, 0x4b, 0x72, 0xf4, 0x39, 0xb9, 0xa5, 0xca, 0x39, 0xe5, 0x90,
0xe5, 0x90, 0x63, 0x6e, 0x39, 0xe4, 0x92, 0xaa, 0xe4, 0x17, 0xe4, 0x57, 0xa4, 0xa6, 0x67, 0x16, 0x63, 0x6e, 0x39, 0xe4, 0x92, 0xaa, 0xe4, 0x17, 0xe4, 0x57, 0xa4, 0xa6, 0x67, 0x16, 0xd8, 0x05,
0xbb, 0x0b, 0x02, 0x8c, 0x54, 0x39, 0xe5, 0xc4, 0xe9, 0xde, 0xee, 0x9e, 0x9e, 0xfe, 0x6e, 0x10, 0x01, 0x86, 0xaa, 0x9c, 0x72, 0xe2, 0x74, 0x6f, 0x77, 0x4f, 0x4f, 0xbf, 0x1b, 0x84, 0x66, 0xcc,
0x36, 0x63, 0xe6, 0x27, 0x7e, 0x78, 0xbe, 0x17, 0x27, 0x91, 0x8c, 0x48, 0x1d, 0xff, 0x9c, 0xcd, 0xfc, 0xc4, 0x0f, 0xcf, 0xf6, 0xe2, 0x24, 0x92, 0x11, 0xa9, 0xe2, 0x9f, 0xd3, 0xd9, 0x8b, 0x9d,
0x9e, 0xef, 0xdc, 0x14, 0xf3, 0xd0, 0x75, 0x04, 0x97, 0xd2, 0x0f, 0xcf, 0x85, 0xfe, 0xbc, 0x63, 0x5b, 0x62, 0x1e, 0xba, 0x8e, 0xe0, 0x52, 0xfa, 0xe1, 0x99, 0xd0, 0x9f, 0x77, 0x6c, 0x16, 0xc7,
0xb3, 0x38, 0x0e, 0x7c, 0x97, 0x49, 0x3f, 0x0a, 0x9d, 0x29, 0x97, 0xcc, 0x63, 0x92, 0x39, 0x53, 0x81, 0xef, 0x32, 0xe9, 0x47, 0xa1, 0x33, 0xe5, 0x92, 0x79, 0x4c, 0x32, 0x67, 0xca, 0x85, 0x60,
0x2e, 0x04, 0x3b, 0xe7, 0x9a, 0xc6, 0x66, 0x70, 0xf7, 0x87, 0x5c, 0xba, 0x13, 0x3f, 0x3c, 0x7f, 0x67, 0x5c, 0xd3, 0xd8, 0x0c, 0xee, 0xfd, 0x98, 0x4b, 0x77, 0xe2, 0x87, 0x67, 0x4f, 0x98, 0x7b,
0xcc, 0xdc, 0x0b, 0xee, 0x8d, 0xe3, 0x01, 0x93, 0x6c, 0xc0, 0x25, 0xf3, 0x03, 0x41, 0xee, 0x41, 0xce, 0xbd, 0x71, 0xdc, 0x67, 0x92, 0xf5, 0xb9, 0x64, 0x7e, 0x20, 0xc8, 0x7d, 0xa8, 0x23, 0x53,
0x13, 0x99, 0xc2, 0xd9, 0xf4, 0x8c, 0x27, 0x5d, 0xeb, 0xbe, 0xf5, 0x70, 0x93, 0x82, 0x42, 0x8d, 0x38, 0x9b, 0x9e, 0xf2, 0xa4, 0x63, 0x3d, 0xb0, 0x1e, 0x35, 0x29, 0x28, 0xd4, 0x08, 0x31, 0xe4,
0x10, 0x43, 0x1e, 0x40, 0x4b, 0x46, 0x92, 0x05, 0x29, 0x45, 0x09, 0x29, 0x9a, 0x88, 0xd3, 0x24, 0x21, 0x34, 0x64, 0x24, 0x59, 0x90, 0x52, 0x14, 0x90, 0xa2, 0x8e, 0x38, 0x4d, 0x62, 0xff, 0xbd,
0xf6, 0xdf, 0x2a, 0x50, 0x55, 0xb2, 0x67, 0x31, 0xd9, 0x86, 0x0d, 0x37, 0x88, 0xdc, 0x0b, 0x14, 0x04, 0x65, 0x25, 0x7b, 0x16, 0x93, 0x6d, 0xb8, 0xe1, 0x06, 0x91, 0x7b, 0x8e, 0x82, 0x4a, 0x54,
0x54, 0xa1, 0x1a, 0x20, 0x6d, 0x28, 0xf9, 0x1e, 0x72, 0x36, 0x68, 0xc9, 0xf7, 0xc8, 0xa7, 0x50, 0x03, 0xa4, 0x05, 0x05, 0xdf, 0x43, 0xce, 0x1a, 0x2d, 0xf8, 0x1e, 0xf9, 0x14, 0xaa, 0x6e, 0x14,
0x77, 0xa3, 0x50, 0x32, 0x57, 0x8a, 0x6e, 0xf9, 0x7e, 0xf9, 0x61, 0xf3, 0xd1, 0x9b, 0x7b, 0xe9, 0x4a, 0xe6, 0x4a, 0xd1, 0x29, 0x3e, 0x28, 0x3e, 0xaa, 0x3f, 0x7e, 0x73, 0x2f, 0x7d, 0xe9, 0xde,
0x4b, 0xf7, 0x4e, 0xe6, 0xa1, 0xbb, 0x1f, 0x0a, 0xc9, 0x82, 0x00, 0x1f, 0xd6, 0xd7, 0x94, 0xcf, 0xf1, 0x3c, 0x74, 0x87, 0xa1, 0x90, 0x2c, 0x08, 0xf0, 0x61, 0x3d, 0x4d, 0xf9, 0xfc, 0x31, 0x5d,
0x1e, 0xd1, 0x05, 0x13, 0xf9, 0x01, 0x34, 0xdd, 0x68, 0x3a, 0x9d, 0x85, 0xbe, 0xf4, 0xb9, 0xe8, 0x30, 0x91, 0x1f, 0x41, 0xdd, 0x8d, 0xa6, 0xd3, 0x59, 0xe8, 0x4b, 0x9f, 0x8b, 0x4e, 0x09, 0x65,
0x56, 0x50, 0xc6, 0xed, 0xa2, 0x8c, 0xbe, 0x21, 0x98, 0xd3, 0x3c, 0x2d, 0x39, 0x82, 0xad, 0x54, 0xdc, 0xc9, 0xcb, 0xe8, 0x19, 0x82, 0x39, 0xcd, 0xd2, 0x92, 0x43, 0xd8, 0x4a, 0xc5, 0x18, 0x1b,
0x8c, 0xb1, 0x41, 0x77, 0xe3, 0xbe, 0xf5, 0xb0, 0xf9, 0xe8, 0xed, 0x8c, 0xfd, 0x1a, 0x83, 0xd1, 0x74, 0x6e, 0x3c, 0xb0, 0x1e, 0xd5, 0x1f, 0xbf, 0xbd, 0x64, 0xbf, 0xc2, 0x60, 0x74, 0x95, 0x9b,
0x65, 0x6e, 0x32, 0x06, 0x92, 0x93, 0x9f, 0xca, 0xac, 0xbe, 0x8a, 0xcc, 0x15, 0x02, 0xc8, 0xfb, 0x8c, 0x81, 0x64, 0xe4, 0xa7, 0x32, 0xcb, 0xaf, 0x22, 0x73, 0x8d, 0x00, 0xf2, 0x3e, 0x54, 0xe2,
0x50, 0x8b, 0x93, 0xe8, 0xb9, 0x1f, 0xf0, 0x6e, 0x0d, 0x65, 0xdd, 0xc9, 0x64, 0xa5, 0x32, 0x8e, 0x24, 0x7a, 0xe1, 0x07, 0xbc, 0x53, 0x41, 0x59, 0x77, 0x97, 0xb2, 0x52, 0x19, 0x47, 0x9a, 0x80,
0x35, 0x01, 0x4d, 0x29, 0xc9, 0x21, 0xb4, 0xcd, 0x31, 0xd5, 0xa3, 0xfe, 0x2a, 0x7a, 0x2c, 0x31, 0xa6, 0x94, 0xe4, 0x00, 0x5a, 0xe6, 0x98, 0xea, 0x51, 0x7d, 0x15, 0x3d, 0x56, 0x98, 0xc9, 0x7b,
0x93, 0xf7, 0xa0, 0x66, 0x22, 0xae, 0xdb, 0x40, 0x39, 0xaf, 0x17, 0x4d, 0x7c, 0xa2, 0x3f, 0xd2, 0x50, 0x31, 0x11, 0xd7, 0xa9, 0xa1, 0x9c, 0xd7, 0xf3, 0x26, 0x3e, 0xd6, 0x1f, 0x69, 0x4a, 0xa5,
0x94, 0x4a, 0x19, 0x37, 0x0d, 0xd1, 0x54, 0x01, 0x78, 0x25, 0xe3, 0x2e, 0x71, 0xdb, 0x7f, 0xac, 0x8c, 0x9b, 0x86, 0x68, 0xaa, 0x00, 0xbc, 0x92, 0x71, 0x57, 0xb8, 0xed, 0x3f, 0x95, 0xa0, 0x71,
0x40, 0xeb, 0x70, 0x16, 0x48, 0xbf, 0xe7, 0xba, 0xd1, 0x2c, 0x94, 0x84, 0x40, 0x25, 0x64, 0x53, 0x30, 0x0b, 0xa4, 0xdf, 0x75, 0xdd, 0x68, 0x16, 0x4a, 0x42, 0xa0, 0x14, 0xb2, 0x29, 0xc7, 0xf8,
0x8e, 0xf1, 0xd5, 0xa0, 0x78, 0x26, 0xbb, 0xd0, 0x90, 0xfe, 0x94, 0x0b, 0xc9, 0xa6, 0x31, 0x46, 0xaa, 0x51, 0x3c, 0x93, 0x5d, 0xa8, 0x49, 0x7f, 0xca, 0x85, 0x64, 0xd3, 0x18, 0xa3, 0xac, 0x48,
0x59, 0x99, 0x66, 0x08, 0xf5, 0xd5, 0xf7, 0x78, 0x28, 0x7d, 0x37, 0x0a, 0xbb, 0x65, 0x64, 0xcb, 0x97, 0x08, 0xf5, 0xd5, 0xf7, 0x78, 0x28, 0x7d, 0x37, 0x0a, 0x3b, 0x45, 0x64, 0x5b, 0x22, 0xc8,
0x10, 0xe4, 0x33, 0x00, 0x37, 0x0a, 0xa2, 0xc4, 0x99, 0x30, 0x31, 0x31, 0x81, 0xf4, 0x20, 0x53, 0x67, 0x00, 0x6e, 0x14, 0x44, 0x89, 0x33, 0x61, 0x62, 0x62, 0x02, 0xe9, 0xe1, 0x52, 0xd9, 0xec,
0x36, 0x7f, 0xf7, 0x5e, 0x3f, 0x0a, 0xa2, 0x59, 0xf2, 0x84, 0x89, 0x09, 0x6d, 0x20, 0x93, 0x3a, 0xdd, 0x7b, 0xbd, 0x28, 0x88, 0x66, 0xc9, 0x53, 0x26, 0x26, 0xb4, 0x86, 0x4c, 0xea, 0x48, 0x3a,
0x92, 0x2e, 0xd4, 0x10, 0xd8, 0xf7, 0x30, 0x90, 0xca, 0x34, 0x05, 0xc9, 0x3b, 0xb0, 0x75, 0xc1, 0x50, 0x41, 0x60, 0xe8, 0x61, 0x20, 0x15, 0x69, 0x0a, 0x92, 0x77, 0x60, 0xeb, 0x9c, 0xcf, 0x5d,
0xe7, 0x2e, 0x4b, 0x3c, 0xc7, 0xa4, 0x35, 0x86, 0x45, 0x83, 0xb6, 0x0d, 0xfa, 0x58, 0x63, 0xc9, 0x96, 0x78, 0x8e, 0x49, 0x6b, 0x0c, 0x8b, 0x1a, 0x6d, 0x19, 0xf4, 0x91, 0xc6, 0x92, 0x3b, 0x50,
0x6d, 0xa8, 0x5d, 0xf0, 0xb9, 0x33, 0xf3, 0x3d, 0xf4, 0x75, 0x83, 0x56, 0x2f, 0xf8, 0x7c, 0xec, 0x39, 0xe7, 0x73, 0x67, 0xe6, 0x7b, 0xe8, 0xeb, 0x1a, 0x2d, 0x9f, 0xf3, 0xf9, 0xd8, 0xf7, 0xc8,
0x7b, 0xe4, 0x63, 0xa8, 0xfa, 0x53, 0x76, 0xce, 0x95, 0x1f, 0x95, 0x66, 0x6f, 0xad, 0xd1, 0x6c, 0xc7, 0x50, 0xf6, 0xa7, 0xec, 0x8c, 0x2b, 0x3f, 0x2a, 0xcd, 0xde, 0xda, 0xa0, 0xd9, 0x10, 0xdf,
0x1f, 0xdf, 0x23, 0xe7, 0xfb, 0x8a, 0x98, 0x1a, 0x9e, 0x1d, 0x1b, 0x20, 0x53, 0x59, 0xa5, 0xa6, 0x23, 0xe7, 0x43, 0x45, 0x4c, 0x0d, 0xcf, 0x8e, 0x0d, 0xb0, 0x54, 0x59, 0xa5, 0xa6, 0x1f, 0x7a,
0x1f, 0x7a, 0xfc, 0x67, 0x5d, 0xeb, 0x7e, 0xf9, 0x61, 0x99, 0x6a, 0x60, 0xe7, 0xef, 0x16, 0x6c, 0xfc, 0x17, 0x1d, 0xeb, 0x41, 0xf1, 0x51, 0x91, 0x6a, 0x60, 0xe7, 0x1f, 0x16, 0x34, 0x73, 0xdc,
0x16, 0xb8, 0xf3, 0xca, 0x58, 0x05, 0x65, 0x52, 0xd3, 0x97, 0x72, 0xa6, 0xef, 0x42, 0x2d, 0x66, 0x59, 0x65, 0xac, 0x9c, 0x32, 0xa9, 0xe9, 0x0b, 0x19, 0xd3, 0x77, 0xa0, 0x12, 0xb3, 0x79, 0x10,
0xf3, 0x20, 0x62, 0x1e, 0x9a, 0xb6, 0x45, 0x53, 0x50, 0x5d, 0xf7, 0xc2, 0xf7, 0xa4, 0xb2, 0xa9, 0x31, 0x0f, 0x4d, 0xdb, 0xa0, 0x29, 0xa8, 0xae, 0x7b, 0xe9, 0x7b, 0x52, 0xd9, 0x54, 0x19, 0x45,
0x32, 0x8a, 0x06, 0xc8, 0x2d, 0xa8, 0x4e, 0xb8, 0x7f, 0x3e, 0x91, 0xc6, 0x56, 0x06, 0x22, 0x3b, 0x03, 0xe4, 0x36, 0x94, 0x27, 0xdc, 0x3f, 0x9b, 0x48, 0x63, 0x2b, 0x03, 0x91, 0x1d, 0xa8, 0xaa,
0x50, 0x57, 0x81, 0x27, 0xfc, 0x9f, 0x73, 0xb4, 0x51, 0x99, 0x2e, 0x60, 0xf2, 0x26, 0x6c, 0x26, 0xc0, 0x13, 0xfe, 0x2f, 0x39, 0xda, 0xa8, 0x48, 0x17, 0x30, 0x79, 0x13, 0x9a, 0x09, 0x9e, 0x1c,
0x78, 0x72, 0x24, 0x4b, 0xce, 0xb9, 0x44, 0x1b, 0x95, 0x69, 0x4b, 0x23, 0x4f, 0x11, 0x97, 0x15, 0xc9, 0x92, 0x33, 0x2e, 0xd1, 0x46, 0x45, 0xda, 0xd0, 0xc8, 0x13, 0xc4, 0x2d, 0x0b, 0x4f, 0x35,
0x9e, 0x7a, 0xae, 0xf0, 0xd8, 0x7f, 0xb5, 0xe0, 0xe6, 0xd3, 0xc8, 0x65, 0x81, 0xb1, 0xf4, 0xb1, 0x53, 0x78, 0xec, 0xbf, 0x59, 0x70, 0xeb, 0x59, 0xe4, 0xb2, 0xc0, 0x58, 0xfa, 0xc8, 0x28, 0xf7,
0x51, 0xee, 0xbb, 0x50, 0xb9, 0xe0, 0x73, 0x81, 0xa6, 0x28, 0xf8, 0x7b, 0x05, 0xf1, 0xde, 0x01, 0x7d, 0x28, 0x9d, 0xf3, 0xb9, 0x40, 0x53, 0xe4, 0xfc, 0xbd, 0x86, 0x78, 0x6f, 0x9f, 0xcf, 0x29,
0x9f, 0x53, 0x24, 0x27, 0x1f, 0x42, 0x6b, 0xaa, 0xcc, 0xce, 0xb4, 0xd9, 0xd1, 0x12, 0xcd, 0x47, 0x92, 0x93, 0x0f, 0xa1, 0x31, 0x55, 0x66, 0x67, 0xda, 0xec, 0x68, 0x89, 0xfa, 0xe3, 0xdb, 0xeb,
0xb7, 0x56, 0x3b, 0x85, 0x16, 0x68, 0xd5, 0x0b, 0x63, 0x26, 0xc4, 0x8b, 0x28, 0xf1, 0x4c, 0x14, 0x9d, 0x42, 0x73, 0xb4, 0xea, 0x85, 0x31, 0x13, 0xe2, 0x65, 0x94, 0x78, 0x26, 0x0a, 0x17, 0xf0,
0x2e, 0xe0, 0x9d, 0xff, 0x85, 0xf2, 0x01, 0x9f, 0xaf, 0x8c, 0x6d, 0x02, 0x15, 0x55, 0x8c, 0xf1, 0xce, 0xff, 0x43, 0x71, 0x9f, 0xcf, 0xd7, 0xc6, 0x36, 0x81, 0x92, 0x2a, 0xc6, 0x78, 0x55, 0x83,
0xaa, 0x16, 0xc5, 0xb3, 0xfd, 0x4b, 0x0b, 0x3a, 0x4a, 0xc7, 0x7c, 0x95, 0x5c, 0x53, 0x79, 0xdf, 0xe2, 0xd9, 0xfe, 0xc6, 0x82, 0xb6, 0xd2, 0x31, 0x5b, 0x25, 0x37, 0x54, 0xde, 0x77, 0x60, 0xcb,
0x81, 0x2d, 0x3f, 0x47, 0xe5, 0x2c, 0xca, 0x70, 0x3b, 0x8f, 0xde, 0xf7, 0xb0, 0x0f, 0xf0, 0x4b, 0xcf, 0x50, 0x39, 0x8b, 0x32, 0xdc, 0xca, 0xa2, 0x87, 0x1e, 0xf6, 0x01, 0x7e, 0xe1, 0xbb, 0xdc,
0xdf, 0xe5, 0x8e, 0x9c, 0xc7, 0xdc, 0x68, 0x08, 0x1a, 0x75, 0x3a, 0x8f, 0xf9, 0x42, 0xb9, 0x4a, 0x91, 0xf3, 0x98, 0x1b, 0x0d, 0x41, 0xa3, 0x4e, 0xe6, 0x31, 0x5f, 0x28, 0x57, 0xca, 0x7b, 0xff,
0xa6, 0x9c, 0xfd, 0x4f, 0x0b, 0x6e, 0xaf, 0x29, 0xd7, 0x2f, 0xd9, 0x09, 0xde, 0x84, 0x4d, 0x53, 0x82, 0x27, 0xc2, 0x8f, 0x42, 0x74, 0x67, 0x93, 0xa6, 0xa0, 0xfd, 0x2f, 0x0b, 0xee, 0x6c, 0x28,
0x73, 0x1c, 0x0c, 0x5a, 0x73, 0x71, 0xcb, 0x20, 0x75, 0x44, 0xde, 0x81, 0x3a, 0x0f, 0x85, 0x93, 0xe4, 0xd7, 0xec, 0x11, 0x6f, 0x42, 0xd3, 0x54, 0x23, 0x07, 0xc3, 0xd9, 0xa8, 0xd4, 0x30, 0x48,
0xbb, 0xbe, 0xc6, 0x43, 0x31, 0x52, 0xe6, 0x79, 0x00, 0xad, 0x80, 0x09, 0xe9, 0xcc, 0x62, 0x8f, 0x1d, 0xab, 0x77, 0xa1, 0xca, 0x43, 0xe1, 0x64, 0x14, 0xab, 0xf0, 0x50, 0x8c, 0x94, 0x6e, 0x0f,
0x49, 0xae, 0x33, 0xb0, 0x42, 0x9b, 0x0a, 0x37, 0xd6, 0x28, 0xf5, 0x32, 0x31, 0x17, 0x92, 0x4f, 0xa1, 0x11, 0x30, 0x21, 0x9d, 0x59, 0xec, 0x31, 0xc9, 0x75, 0x6e, 0x96, 0x68, 0x5d, 0xe1, 0xc6,
0x1d, 0xc9, 0xce, 0x55, 0x61, 0x2e, 0xab, 0x97, 0x69, 0xd4, 0x29, 0x3b, 0x17, 0xe4, 0x6d, 0x68, 0x1a, 0xa5, 0xde, 0x2c, 0xe6, 0x42, 0xf2, 0xa9, 0x23, 0xd9, 0x99, 0x2a, 0xd9, 0x45, 0xf5, 0x66,
0x07, 0xca, 0xed, 0x4e, 0xe8, 0xbb, 0x17, 0x78, 0x89, 0x4e, 0xc2, 0x4d, 0xc4, 0x8e, 0x0c, 0xd2, 0x8d, 0x3a, 0x61, 0x67, 0x82, 0xbc, 0x0d, 0xad, 0x40, 0x05, 0x84, 0x13, 0xfa, 0xee, 0x39, 0x5e,
0xfe, 0x45, 0x15, 0xee, 0xac, 0xed, 0x4d, 0xe4, 0xff, 0x60, 0x3b, 0xaf, 0x88, 0x83, 0xbc, 0xc1, 0xa2, 0xd3, 0xb3, 0x89, 0xd8, 0x91, 0x41, 0xda, 0xbf, 0x2a, 0xc3, 0xdd, 0x8d, 0x5d, 0x8b, 0x7c,
0xdc, 0xbc, 0x9e, 0xe4, 0x14, 0x7a, 0xaa, 0xbf, 0xfc, 0x17, 0x9b, 0x42, 0xf9, 0x96, 0x79, 0x1e, 0x07, 0xb6, 0xb3, 0x8a, 0x38, 0xc8, 0x1b, 0xcc, 0xcd, 0xeb, 0x49, 0x46, 0xa1, 0x67, 0xfa, 0xcb,
0xf7, 0xb0, 0x2b, 0xd4, 0xa9, 0x06, 0x54, 0x2d, 0x38, 0x53, 0x4e, 0xe6, 0x1e, 0x16, 0xfd, 0x3a, 0xff, 0xb0, 0x29, 0x94, 0x6f, 0x99, 0xe7, 0x71, 0x0f, 0xfb, 0x45, 0x95, 0x6a, 0x40, 0xc5, 0xc9,
0x4d, 0x41, 0x45, 0x3f, 0x9d, 0x29, 0x9d, 0x9a, 0x9a, 0x1e, 0x01, 0x45, 0x9f, 0xf0, 0x69, 0x74, 0xa9, 0x72, 0x32, 0xf7, 0xb0, 0x1d, 0x54, 0x69, 0x0a, 0x2a, 0xfa, 0xe9, 0x4c, 0xe9, 0x54, 0xd7,
0xc9, 0xbd, 0x6e, 0x4b, 0xd3, 0x1b, 0x90, 0xdc, 0x87, 0xd6, 0x84, 0x09, 0x07, 0xc5, 0x3a, 0x33, 0xf4, 0x08, 0x28, 0xfa, 0x84, 0x4f, 0xa3, 0x0b, 0xee, 0x75, 0x1a, 0x9a, 0xde, 0x80, 0xe4, 0x01,
0xd1, 0xdd, 0xc4, 0xcf, 0x30, 0x61, 0xa2, 0xa7, 0x50, 0x63, 0xd5, 0x99, 0x6e, 0x5e, 0xf2, 0xc4, 0x34, 0x26, 0x4c, 0x38, 0x28, 0xd6, 0x99, 0x89, 0x4e, 0x13, 0x3f, 0xc3, 0x84, 0x89, 0xae, 0x42,
0x7f, 0x9e, 0x0e, 0x3f, 0x42, 0x32, 0x39, 0x13, 0xdd, 0x36, 0x56, 0x06, 0x92, 0xff, 0x74, 0x82, 0x8d, 0x55, 0xcf, 0xba, 0x75, 0xc1, 0x13, 0xff, 0x45, 0x3a, 0x16, 0x09, 0xc9, 0xe4, 0x4c, 0x74,
0x5f, 0x70, 0x8c, 0x49, 0x66, 0x42, 0xa6, 0x94, 0x5b, 0x48, 0xd9, 0x44, 0x9c, 0x21, 0xf9, 0x04, 0x5a, 0x58, 0x33, 0x48, 0xf6, 0xd3, 0x31, 0x7e, 0xc1, 0x01, 0x27, 0x99, 0x09, 0x99, 0x52, 0x6e,
0xee, 0x9a, 0xde, 0xee, 0x24, 0xfc, 0xa7, 0x33, 0x2e, 0xa4, 0xf6, 0x22, 0xb2, 0xf0, 0x6e, 0x07, 0x21, 0x65, 0x1d, 0x71, 0x86, 0xe4, 0x13, 0xb8, 0x67, 0xba, 0xbe, 0x93, 0xf0, 0x9f, 0xcf, 0xb8,
0x39, 0xba, 0x86, 0x84, 0x6a, 0x0a, 0x74, 0xa6, 0xe2, 0xe7, 0xeb, 0xd9, 0x75, 0x1a, 0xdc, 0x58, 0x90, 0xda, 0x8b, 0xc8, 0xc2, 0x3b, 0x6d, 0xe4, 0xe8, 0x18, 0x12, 0xaa, 0x29, 0xd0, 0x99, 0x8a,
0xcb, 0xde, 0xc7, 0xcc, 0xf8, 0x14, 0x76, 0x97, 0xd9, 0x95, 0x39, 0x24, 0x37, 0xd7, 0x13, 0xe4, 0x9f, 0x6f, 0x66, 0xd7, 0x69, 0x70, 0x73, 0x23, 0x7b, 0x0f, 0x33, 0xe3, 0x53, 0xd8, 0x5d, 0x65,
0xbf, 0x53, 0xe4, 0xa7, 0x48, 0xa1, 0xef, 0x5f, 0x2f, 0x40, 0x2b, 0x70, 0x73, 0xbd, 0x00, 0xad, 0x57, 0xe6, 0x90, 0xdc, 0x5c, 0x4f, 0x90, 0xff, 0x6e, 0x9e, 0x9f, 0x22, 0x85, 0xbe, 0x7f, 0xb3,
0xc1, 0x03, 0x68, 0x79, 0xbe, 0x88, 0x03, 0x36, 0xd7, 0xf1, 0xb5, 0x8d, 0xae, 0x6f, 0x1a, 0x9c, 0x00, 0xad, 0xc0, 0xad, 0xcd, 0x02, 0xb4, 0x06, 0x0f, 0xa1, 0xe1, 0xf9, 0x22, 0x0e, 0xd8, 0x5c,
0x8a, 0x31, 0xfb, 0xc5, 0xd5, 0x7c, 0x4f, 0x1b, 0xf3, 0xea, 0x7c, 0xbf, 0x12, 0xd4, 0xa5, 0x15, 0xc7, 0xd7, 0x36, 0xba, 0xbe, 0x6e, 0x70, 0x2a, 0xc6, 0xec, 0x97, 0x97, 0xf3, 0x3d, 0x6d, 0xd9,
0x41, 0xbd, 0x1c, 0xb9, 0xe5, 0x2b, 0x91, 0x6b, 0x3f, 0x86, 0x9d, 0xe5, 0x8b, 0x8f, 0x67, 0x67, 0xeb, 0xf3, 0xfd, 0x52, 0x50, 0x17, 0xd6, 0x04, 0xf5, 0x6a, 0xe4, 0x16, 0x2f, 0x45, 0xae, 0xfd,
0x81, 0xef, 0xf6, 0x27, 0xec, 0x25, 0x6b, 0x8d, 0xfd, 0x6d, 0x19, 0x36, 0x0b, 0x83, 0xe1, 0xbf, 0x04, 0x76, 0x56, 0x2f, 0x3e, 0x9a, 0x9d, 0x06, 0xbe, 0xdb, 0x9b, 0xb0, 0x6b, 0xd6, 0x1a, 0xfb,
0xe5, 0x6b, 0x61, 0x62, 0xde, 0x83, 0x66, 0x9c, 0xf8, 0x97, 0x4c, 0x72, 0xe7, 0x82, 0xcf, 0x4d, 0xdb, 0x22, 0x34, 0x73, 0x23, 0xe3, 0x7f, 0xe4, 0x6b, 0x60, 0x62, 0xde, 0x87, 0x7a, 0x9c, 0xf8,
0x9f, 0x03, 0x83, 0x52, 0x75, 0xfb, 0xbe, 0xaa, 0x9d, 0xc2, 0x4d, 0xfc, 0x58, 0xe9, 0x85, 0x79, 0x17, 0x4c, 0x72, 0xe7, 0x9c, 0xcf, 0x4d, 0x07, 0x04, 0x83, 0x52, 0x15, 0xfd, 0x81, 0xaa, 0xaa,
0xd9, 0xa2, 0x79, 0x94, 0x6a, 0x7b, 0x3f, 0x89, 0xfc, 0xd0, 0x64, 0x65, 0x9d, 0x1a, 0x48, 0x35, 0xc2, 0x4d, 0xfc, 0x58, 0xe9, 0x85, 0x79, 0xd9, 0xa0, 0x59, 0x94, 0x6a, 0x88, 0x3f, 0x8b, 0xfc,
0x05, 0x1d, 0xab, 0xdc, 0xc3, 0xb6, 0x57, 0xa7, 0x0b, 0x38, 0x4b, 0x9a, 0x5a, 0x3e, 0x69, 0x8e, 0xd0, 0x64, 0x65, 0x95, 0x1a, 0x48, 0xb5, 0x0b, 0x1d, 0xab, 0xdc, 0xc3, 0x86, 0x58, 0xa5, 0x0b,
0xa0, 0x63, 0xbc, 0x2b, 0x1c, 0x19, 0x39, 0x4a, 0x8e, 0x99, 0x0d, 0xde, 0x5e, 0x37, 0xfe, 0x1a, 0x78, 0x99, 0x34, 0x95, 0x6c, 0xd2, 0x1c, 0x42, 0xdb, 0x78, 0x57, 0x38, 0x32, 0x72, 0x94, 0x1c,
0xf2, 0xd3, 0xe8, 0x8b, 0xc8, 0x0f, 0x69, 0x3b, 0x29, 0xc0, 0xe4, 0x23, 0xa8, 0xa7, 0x43, 0x97, 0x33, 0x35, 0xbc, 0xbd, 0x69, 0x30, 0x36, 0xe4, 0x27, 0xd1, 0x17, 0x91, 0x1f, 0xd2, 0x56, 0x92,
0x19, 0xf2, 0xee, 0xad, 0x11, 0x64, 0xa6, 0x3d, 0x41, 0x17, 0x0c, 0x6a, 0xb6, 0xe2, 0xa1, 0x9b, 0x83, 0xc9, 0x47, 0x50, 0x4d, 0xc7, 0x31, 0x33, 0xfe, 0xdd, 0xdf, 0x20, 0xc8, 0xcc, 0x81, 0x82,
0xcc, 0x63, 0xb9, 0x48, 0xfa, 0x0c, 0xa1, 0xbe, 0x8a, 0x98, 0xbb, 0x92, 0x65, 0xa9, 0x9f, 0x21, 0x2e, 0x18, 0xd4, 0xd4, 0xc5, 0x43, 0x37, 0x99, 0xc7, 0x72, 0x91, 0xf4, 0x4b, 0x84, 0xfa, 0x2a,
0x54, 0x6b, 0x32, 0xa4, 0x2a, 0x81, 0xb1, 0x1d, 0xb7, 0xd0, 0x72, 0xed, 0x0c, 0x7d, 0xc0, 0xe7, 0x62, 0xee, 0x4a, 0xb6, 0x4c, 0xfd, 0x25, 0x42, 0x35, 0x2d, 0x43, 0xaa, 0x12, 0x18, 0x1b, 0x75,
0xc2, 0xfe, 0x8b, 0x05, 0x77, 0xaf, 0x79, 0x91, 0xf1, 0x97, 0xb5, 0xf0, 0xd7, 0x1b, 0x00, 0x31, 0x03, 0x2d, 0xd7, 0x5a, 0xa2, 0xf7, 0xf9, 0x5c, 0xd8, 0x7f, 0xb5, 0xe0, 0xde, 0x15, 0x2f, 0x32,
0xc6, 0x06, 0xba, 0x4b, 0xfb, 0xbf, 0xa1, 0x31, 0xca, 0x5b, 0x0b, 0xa7, 0x97, 0xf3, 0x4e, 0xbf, 0xfe, 0xb2, 0x16, 0xfe, 0x7a, 0x03, 0x20, 0xc6, 0xd8, 0x40, 0x77, 0x69, 0xff, 0xd7, 0x34, 0x46,
0xa6, 0xb0, 0xde, 0x86, 0x9a, 0x3b, 0x61, 0x52, 0xf5, 0xce, 0x0d, 0x3d, 0x10, 0x29, 0x70, 0xdf, 0x79, 0x6b, 0xe1, 0xf4, 0x62, 0xd6, 0xe9, 0x57, 0x14, 0xd6, 0x3b, 0x50, 0x71, 0x27, 0x4c, 0xaa,
0x53, 0x71, 0x9b, 0x0e, 0xee, 0x73, 0xf5, 0xb5, 0xaa, 0x1d, 0xbf, 0xc0, 0xed, 0xa3, 0x13, 0x75, 0xae, 0x7a, 0x43, 0x8f, 0x4a, 0x0a, 0x1c, 0x7a, 0x2a, 0x6e, 0xd3, 0x91, 0x7e, 0xae, 0xbe, 0x96,
0xfa, 0xd6, 0xf4, 0x65, 0x08, 0xd8, 0xbf, 0x2a, 0x41, 0x67, 0x39, 0x9c, 0xc9, 0x27, 0xb9, 0xa5, 0xb5, 0xe3, 0x17, 0xb8, 0x21, 0x3a, 0x51, 0xa7, 0x6f, 0x45, 0x5f, 0x86, 0x80, 0xfd, 0xeb, 0x02,
0xe8, 0xca, 0x5c, 0xb2, 0xa6, 0xf1, 0xe4, 0x56, 0xa2, 0xcf, 0xa1, 0x65, 0x5e, 0xad, 0xb4, 0x13, 0xb4, 0x57, 0xc3, 0x99, 0x7c, 0x92, 0x59, 0x97, 0x2e, 0x4d, 0x2c, 0x1b, 0x1a, 0x4f, 0x66, 0x59,
0xdd, 0xd2, 0xf2, 0xc0, 0xb8, 0x3e, 0x7f, 0x68, 0x33, 0x5e, 0x9c, 0x05, 0xf9, 0x08, 0x6a, 0xe9, 0xfa, 0x1c, 0x1a, 0xe6, 0xd5, 0x4a, 0x3b, 0xd1, 0x29, 0xac, 0x8e, 0x92, 0x9b, 0xf3, 0x87, 0xd6,
0x7c, 0x53, 0xc6, 0x78, 0xb8, 0x46, 0x8d, 0x74, 0xd4, 0x49, 0x39, 0xfe, 0x83, 0xc5, 0xcc, 0xfe, 0xe3, 0xc5, 0x59, 0x90, 0x8f, 0xa0, 0x92, 0x4e, 0x3e, 0x45, 0x8c, 0x87, 0x2b, 0xd4, 0x48, 0x87,
0x00, 0xb6, 0xf0, 0xab, 0x52, 0xc8, 0xf4, 0x81, 0x97, 0xcb, 0xeb, 0x8f, 0x61, 0x3b, 0x65, 0x3c, 0xa0, 0x94, 0xe3, 0xbf, 0x58, 0xd9, 0xec, 0x0f, 0x60, 0x0b, 0xbf, 0x2a, 0x85, 0x4c, 0x1f, 0xb8,
0xd4, 0xab, 0xaf, 0xa0, 0x9c, 0xbd, 0x2c, 0xf7, 0x67, 0x70, 0x4b, 0x71, 0xf7, 0x5c, 0xe9, 0x5f, 0x5e, 0x5e, 0x7f, 0x0c, 0xdb, 0x29, 0xe3, 0x81, 0x5e, 0x8a, 0x05, 0xe5, 0xec, 0xba, 0xdc, 0x9f,
0xfa, 0x72, 0xde, 0xe7, 0xa1, 0xe4, 0xc9, 0x35, 0xfc, 0x1d, 0x28, 0xfb, 0x9e, 0x36, 0x6f, 0x8b, 0xc1, 0x6d, 0xc5, 0xdd, 0x75, 0xa5, 0x7f, 0xe1, 0xcb, 0x79, 0x8f, 0x87, 0x92, 0x27, 0x57, 0xf0,
0xaa, 0xa3, 0x3d, 0xd0, 0xb5, 0xa9, 0x28, 0xa1, 0xe7, 0xba, 0x1c, 0x93, 0xe0, 0x65, 0xa5, 0x0c, 0xb7, 0xa1, 0xe8, 0x7b, 0xda, 0xbc, 0x0d, 0xaa, 0x8e, 0x76, 0x5f, 0xd7, 0xa6, 0xbc, 0x84, 0xae,
0x75, 0x90, 0x17, 0xa5, 0x0c, 0x7c, 0x31, 0xf5, 0x85, 0x78, 0x05, 0x31, 0xdf, 0x58, 0xd0, 0x52, 0xeb, 0x72, 0x4c, 0x82, 0xeb, 0x4a, 0x19, 0xe8, 0x20, 0xcf, 0x4b, 0xe9, 0xfb, 0x62, 0xea, 0x0b,
0x72, 0x1e, 0x47, 0xd1, 0xc5, 0x94, 0x25, 0x17, 0xeb, 0x19, 0x67, 0x49, 0x60, 0xcc, 0xa0, 0x8e, 0xf1, 0x0a, 0x62, 0xbe, 0xb1, 0xa0, 0xa1, 0xe4, 0x3c, 0x89, 0xa2, 0xf3, 0x29, 0x4b, 0xce, 0x37,
0x8b, 0xf9, 0xae, 0x9c, 0x1b, 0x3e, 0xef, 0x42, 0x03, 0xab, 0xb6, 0xa3, 0x68, 0x75, 0x56, 0xd4, 0x33, 0xce, 0x92, 0xc0, 0x98, 0x41, 0x1d, 0x17, 0x93, 0x5f, 0x31, 0x33, 0xf9, 0xdd, 0x83, 0x1a,
0x11, 0x31, 0x4e, 0x82, 0x7c, 0xfb, 0xde, 0x28, 0xb6, 0xef, 0x37, 0x00, 0x3c, 0x1e, 0x70, 0x35, 0x56, 0x6d, 0x47, 0xd1, 0xea, 0xac, 0xa8, 0x22, 0x62, 0x9c, 0x04, 0xd9, 0xf6, 0x7d, 0x23, 0xdf,
0x06, 0x31, 0x89, 0x59, 0x51, 0xa1, 0x0d, 0x83, 0xe9, 0x49, 0xfb, 0x0b, 0x1d, 0xfc, 0xfd, 0x80, 0xbe, 0xdf, 0x00, 0xf0, 0x78, 0xc0, 0xd5, 0x18, 0xc4, 0x24, 0x66, 0x45, 0x89, 0xd6, 0x0c, 0xa6,
0xb3, 0xe4, 0x89, 0x2f, 0x64, 0x94, 0xcc, 0xf3, 0x39, 0x66, 0x15, 0x72, 0xec, 0x0d, 0x00, 0x57, 0x2b, 0xed, 0x2f, 0x74, 0xf0, 0xf7, 0x02, 0xce, 0x92, 0xa7, 0xbe, 0x90, 0x51, 0x32, 0xcf, 0xe6,
0x11, 0x6a, 0x59, 0x25, 0x2d, 0xcb, 0x60, 0x7a, 0xd2, 0xfe, 0xb3, 0x05, 0x44, 0x09, 0x33, 0x9b, 0x98, 0x95, 0xcb, 0xb1, 0x37, 0x00, 0x5c, 0x45, 0xa8, 0x65, 0x15, 0xb4, 0x2c, 0x83, 0xe9, 0x4a,
0xf0, 0xb1, 0xef, 0xca, 0x59, 0xc2, 0x57, 0x4e, 0xd2, 0xb9, 0x55, 0xa5, 0xb4, 0x66, 0x55, 0x29, 0xfb, 0x2f, 0x16, 0x10, 0x25, 0xcc, 0xec, 0xc8, 0x47, 0xbe, 0x2b, 0x67, 0x09, 0x5f, 0x3b, 0x63,
0xe3, 0x6f, 0x1b, 0x57, 0x56, 0x95, 0x0a, 0xa2, 0xd3, 0x55, 0xe5, 0x2e, 0x34, 0xb0, 0x9f, 0xe1, 0x67, 0x96, 0x98, 0xc2, 0x86, 0x25, 0xa6, 0x88, 0xe3, 0xed, 0xa5, 0x25, 0xa6, 0x84, 0xe8, 0x74,
0xae, 0xb2, 0x81, 0x9f, 0x70, 0x57, 0x39, 0x59, 0xb9, 0xab, 0x54, 0x91, 0x60, 0xcd, 0xae, 0x52, 0x89, 0xb9, 0x07, 0x35, 0xec, 0x67, 0xb8, 0xc5, 0xe8, 0x81, 0x18, 0xb7, 0x98, 0xe3, 0xb5, 0x5b,
0xcb, 0xef, 0x2a, 0x13, 0xb8, 0x79, 0xf5, 0x25, 0x62, 0xfd, 0x3a, 0xf6, 0x7d, 0xa8, 0xc7, 0x86, 0x4c, 0x19, 0x09, 0x36, 0x6c, 0x31, 0x95, 0xec, 0x16, 0x33, 0x81, 0x5b, 0x97, 0x5f, 0x22, 0x36,
0xc8, 0x24, 0xfb, 0x6e, 0x31, 0xcf, 0x8a, 0x92, 0xe8, 0x82, 0xda, 0xfe, 0x5d, 0x09, 0x6e, 0x28, 0x2f, 0x6a, 0x3f, 0x84, 0x6a, 0x6c, 0x88, 0x4c, 0xb2, 0xef, 0xe6, 0xf3, 0x2c, 0x2f, 0x89, 0x2e,
0x82, 0x2f, 0x59, 0x10, 0x70, 0x79, 0x7d, 0x03, 0xef, 0x42, 0x8d, 0x79, 0x5e, 0xc2, 0x85, 0x48, 0xa8, 0xed, 0xdf, 0x17, 0xe0, 0xa6, 0x22, 0xf8, 0x92, 0x05, 0x01, 0x97, 0x57, 0x37, 0xf0, 0x0e,
0xad, 0x66, 0x40, 0x65, 0x9f, 0x17, 0x28, 0x00, 0xcd, 0x56, 0xa7, 0x06, 0x52, 0xb6, 0x57, 0xbe, 0x54, 0x98, 0xe7, 0x25, 0x5c, 0x88, 0xd4, 0x6a, 0x06, 0x54, 0xf6, 0x79, 0x89, 0x02, 0xd0, 0x6c,
0x43, 0xab, 0xd5, 0x29, 0x9e, 0x15, 0x0e, 0xd7, 0x0a, 0x5d, 0x3f, 0xf1, 0xac, 0x24, 0x2b, 0xdf, 0x55, 0x6a, 0x20, 0x65, 0x7b, 0xe5, 0x3b, 0xb4, 0x5a, 0x95, 0xe2, 0x59, 0xe1, 0x70, 0xe1, 0xd0,
0xab, 0xa1, 0x40, 0x6f, 0xc5, 0x29, 0xa8, 0xa8, 0x63, 0x26, 0x27, 0x66, 0xf6, 0xc4, 0xb3, 0xea, 0xf5, 0x13, 0xcf, 0x4a, 0xb2, 0xf2, 0xbd, 0x1a, 0x0a, 0xf4, 0xbe, 0x9c, 0x82, 0x8a, 0x3a, 0x66,
0x25, 0x8b, 0x12, 0x8e, 0x3b, 0x5e, 0x2b, 0x5f, 0xd3, 0x53, 0x7f, 0x37, 0x72, 0xfe, 0x56, 0xef, 0x72, 0x62, 0x66, 0x4f, 0x3c, 0xab, 0x5e, 0xb2, 0x28, 0xe1, 0xb8, 0xfd, 0x35, 0xb2, 0x35, 0x3d,
0x51, 0x8b, 0x38, 0xf6, 0xa5, 0x06, 0xd5, 0x00, 0x7a, 0xd5, 0xf7, 0x3c, 0x1e, 0x9a, 0x86, 0x64, 0xf5, 0x77, 0x2d, 0xe3, 0x6f, 0xf5, 0x1e, 0xb5, 0xa2, 0x63, 0x5f, 0xaa, 0x51, 0x0d, 0xa0, 0x57,
0xa0, 0xf5, 0xc3, 0xa8, 0x7d, 0xa8, 0x23, 0xac, 0x60, 0x2c, 0x41, 0x3e, 0x80, 0xba, 0xa9, 0x79, 0x7d, 0xcf, 0xe3, 0xa1, 0x69, 0x48, 0x06, 0xda, 0x3c, 0x8c, 0xda, 0x07, 0x3a, 0xc2, 0x72, 0xc6,
0x69, 0xb5, 0xbe, 0x5b, 0xb4, 0x7e, 0x81, 0x9e, 0x2e, 0x88, 0xed, 0x3f, 0x58, 0x3a, 0xfc, 0x4f, 0x12, 0xe4, 0x03, 0xa8, 0x9a, 0x9a, 0x97, 0x56, 0xeb, 0x7b, 0x79, 0xeb, 0xe7, 0xe8, 0xe9, 0x82,
0xd8, 0x25, 0xf7, 0x7a, 0xc6, 0x96, 0x39, 0x2b, 0x5b, 0x45, 0x2b, 0xaf, 0x5a, 0xba, 0x77, 0xa1, 0xd8, 0xfe, 0xa3, 0xa5, 0xc3, 0xff, 0x98, 0x5d, 0x70, 0xaf, 0x6b, 0x6c, 0x99, 0xb1, 0xb2, 0x95,
0xf1, 0x9c, 0x5d, 0x46, 0xb3, 0xc4, 0x97, 0xdc, 0x18, 0x3f, 0x43, 0xa8, 0x4e, 0xe6, 0x4e, 0x98, 0xb7, 0xf2, 0xba, 0x75, 0x7c, 0x17, 0x6a, 0x2f, 0xd8, 0x45, 0x34, 0x4b, 0x7c, 0xc9, 0x8d, 0xf1,
0x8f, 0xbb, 0x5e, 0x05, 0x5d, 0x59, 0x43, 0x78, 0xdf, 0xbb, 0x26, 0x65, 0x1f, 0x40, 0x4b, 0x4f, 0x97, 0x08, 0xd5, 0xc9, 0xdc, 0x09, 0xf3, 0x71, 0x0b, 0x2c, 0xa1, 0x2b, 0x2b, 0x08, 0x0f, 0xbd,
0x5f, 0x4e, 0x3e, 0x32, 0x9b, 0x1a, 0x87, 0xe3, 0xa1, 0xfd, 0x6b, 0x0b, 0x5e, 0x5f, 0x39, 0x0f, 0x2b, 0x52, 0xf6, 0x21, 0x34, 0xf4, 0xf4, 0xe5, 0x64, 0x23, 0xb3, 0xae, 0x71, 0x38, 0x1e, 0xda,
0xac, 0x89, 0x9c, 0xe5, 0xee, 0xa8, 0x5f, 0x50, 0xe8, 0x8e, 0x43, 0xb8, 0x37, 0xd1, 0x05, 0xc0, 0xbf, 0xb1, 0xe0, 0xf5, 0xb5, 0xf3, 0xc0, 0x86, 0xc8, 0x59, 0xed, 0x8e, 0xfa, 0x05, 0xb9, 0xee,
0x61, 0x89, 0x3b, 0xf1, 0x2f, 0xb9, 0x23, 0x66, 0x71, 0x1c, 0x25, 0xd2, 0xe1, 0x21, 0x3b, 0x0b, 0x38, 0x80, 0xfb, 0x13, 0x5d, 0x00, 0x1c, 0x96, 0xb8, 0x13, 0xff, 0x82, 0x3b, 0x62, 0x16, 0xc7,
0xcc, 0x2c, 0x58, 0xa7, 0xbb, 0x86, 0xac, 0xa7, 0xa9, 0x4e, 0x34, 0xd1, 0x50, 0xd3, 0xd8, 0xbf, 0x51, 0x22, 0x1d, 0x1e, 0xb2, 0xd3, 0xc0, 0xcc, 0x82, 0x55, 0xba, 0x6b, 0xc8, 0xba, 0x9a, 0xea,
0xb7, 0x74, 0xeb, 0x38, 0x55, 0xc3, 0xbc, 0x5a, 0x0f, 0x78, 0xf2, 0x92, 0xeb, 0xe7, 0x27, 0x50, 0x58, 0x13, 0x0d, 0x34, 0x8d, 0xfd, 0x07, 0x4b, 0xb7, 0x8e, 0x13, 0x35, 0xcc, 0xab, 0xf5, 0x80,
0x35, 0xfb, 0x80, 0xba, 0xa7, 0xbd, 0x3c, 0x43, 0xe5, 0x04, 0xee, 0x9d, 0x66, 0x9b, 0x02, 0x35, 0x27, 0xd7, 0x5c, 0x3f, 0x3f, 0x81, 0xb2, 0xd9, 0x07, 0xd4, 0x3d, 0xad, 0xd5, 0x19, 0x2a, 0x23,
0x4c, 0xf6, 0x87, 0xd0, 0xcc, 0xa1, 0x49, 0x13, 0x6a, 0xe3, 0xd1, 0xc1, 0xe8, 0xe8, 0xcb, 0x51, 0x70, 0xef, 0x64, 0xb9, 0x29, 0x50, 0xc3, 0x64, 0x7f, 0x08, 0xf5, 0x0c, 0x9a, 0xd4, 0xa1, 0x32,
0xe7, 0x35, 0x05, 0x9c, 0xd2, 0xf1, 0xc9, 0xe9, 0x70, 0xd0, 0xb1, 0xc8, 0x0d, 0xd8, 0x1c, 0x8f, 0x1e, 0xed, 0x8f, 0x0e, 0xbf, 0x1c, 0xb5, 0x5f, 0x53, 0xc0, 0x09, 0x1d, 0x1f, 0x9f, 0x0c, 0xfa,
0x10, 0xfc, 0xf2, 0x88, 0x9e, 0x3e, 0xf9, 0xaa, 0x53, 0xb2, 0xbf, 0x29, 0xeb, 0x59, 0xfa, 0x59, 0x6d, 0x8b, 0xdc, 0x84, 0xe6, 0x78, 0x84, 0xe0, 0x97, 0x87, 0xf4, 0xe4, 0xe9, 0x57, 0xed, 0x82,
0x6e, 0x57, 0x31, 0x83, 0xcd, 0x1a, 0xe5, 0x09, 0x54, 0x9e, 0x27, 0xd1, 0x34, 0x0d, 0x05, 0x75, 0xfd, 0x4d, 0x51, 0xcf, 0xd2, 0xcf, 0x33, 0xbb, 0x8a, 0x19, 0x6c, 0x36, 0x28, 0x4f, 0xa0, 0xf4,
0x56, 0x0f, 0x92, 0x91, 0xa9, 0xd9, 0x25, 0x19, 0xa9, 0xd0, 0x70, 0x27, 0x2a, 0xf2, 0xc2, 0xf3, 0x22, 0x89, 0xa6, 0x69, 0x28, 0xa8, 0xb3, 0x7a, 0x90, 0x8c, 0x4c, 0xcd, 0x2e, 0xc8, 0x48, 0x85,
0x74, 0x8e, 0xc9, 0x10, 0xca, 0x25, 0x66, 0xfa, 0xd3, 0xe5, 0xd4, 0xac, 0x88, 0x0b, 0x5c, 0x0f, 0x86, 0x3b, 0x51, 0x91, 0x17, 0x9e, 0xa5, 0x73, 0xcc, 0x12, 0xa1, 0x5c, 0x62, 0xa6, 0x3f, 0x5d,
0x7f, 0xa6, 0x48, 0xb8, 0x88, 0xa3, 0x50, 0xa4, 0x69, 0xb9, 0x80, 0x55, 0x2d, 0x4e, 0x78, 0x1c, 0x4e, 0xcd, 0x8a, 0xb8, 0xc0, 0x75, 0xf1, 0x07, 0x8c, 0x84, 0x8b, 0x38, 0x0a, 0x45, 0x9a, 0x96,
0xf8, 0x9a, 0x59, 0x87, 0x48, 0xc3, 0x60, 0x7a, 0x92, 0xf0, 0xd5, 0x3b, 0x59, 0x1d, 0x2d, 0xfb, 0x0b, 0x58, 0xd5, 0xe2, 0x84, 0xc7, 0x81, 0xaf, 0x99, 0x75, 0x88, 0xd4, 0x0c, 0xa6, 0x2b, 0x09,
0x9d, 0xa2, 0x65, 0x57, 0xbc, 0x7a, 0xef, 0xd9, 0x95, 0xad, 0x6d, 0xe5, 0x26, 0xa7, 0x7d, 0xd8, 0x5f, 0xbf, 0x93, 0x55, 0xd1, 0xb2, 0xdf, 0xcb, 0x5b, 0x76, 0xcd, 0xab, 0xf7, 0x9e, 0x5f, 0xda,
0x58, 0x34, 0xf0, 0x1f, 0x01, 0xb9, 0xca, 0x79, 0xc5, 0x17, 0xc7, 0xc3, 0xd1, 0x60, 0x7f, 0xf4, 0xda, 0xd6, 0x6e, 0x72, 0xda, 0x87, 0xb5, 0x45, 0x03, 0xff, 0x09, 0x90, 0xcb, 0x9c, 0x97, 0x7c,
0x79, 0xc7, 0x22, 0x2d, 0xa8, 0xf7, 0xfa, 0xfd, 0xe1, 0xb1, 0xf2, 0x4c, 0x49, 0x41, 0x83, 0x61, 0x71, 0x34, 0x18, 0xf5, 0x87, 0xa3, 0xcf, 0xdb, 0x16, 0x69, 0x40, 0xb5, 0xdb, 0xeb, 0x0d, 0x8e,
0xff, 0xe9, 0xfe, 0x68, 0x38, 0xe8, 0x94, 0x15, 0xd4, 0xef, 0x8d, 0xfa, 0xc3, 0xa7, 0xc3, 0x41, 0x94, 0x67, 0x0a, 0x0a, 0xea, 0x0f, 0x7a, 0xcf, 0x86, 0xa3, 0x41, 0xbf, 0x5d, 0x54, 0x50, 0xaf,
0xa7, 0x62, 0xff, 0xc3, 0xd2, 0x9d, 0xbd, 0x5f, 0x58, 0x99, 0x06, 0xdc, 0xf5, 0xc5, 0xfa, 0x5f, 0x3b, 0xea, 0x0d, 0x9e, 0x0d, 0xfa, 0xed, 0x92, 0xfd, 0x4f, 0x4b, 0x77, 0xf6, 0x5e, 0x6e, 0x65,
0x5c, 0x76, 0xa1, 0x61, 0xec, 0xb9, 0x9f, 0x46, 0x5a, 0x86, 0x20, 0x3f, 0x86, 0x2d, 0xcf, 0xf0, 0xea, 0x73, 0xd7, 0x17, 0x9b, 0x7f, 0x8b, 0xd9, 0x85, 0x9a, 0xb1, 0xe7, 0x30, 0x8d, 0xb4, 0x25,
0x3b, 0x85, 0xc8, 0x7b, 0x7f, 0x79, 0x46, 0x5a, 0x75, 0xe5, 0x5e, 0x7a, 0x30, 0xe6, 0x69, 0x7b, 0x82, 0xfc, 0x14, 0xb6, 0x3c, 0xc3, 0xef, 0xe4, 0x22, 0xef, 0xfd, 0xd5, 0x19, 0x69, 0xdd, 0x95,
0x05, 0xd8, 0x7e, 0x17, 0xda, 0x45, 0x8a, 0xc2, 0x63, 0x5f, 0x2b, 0x3c, 0xd6, 0xb2, 0xbf, 0xb5, 0x7b, 0xe9, 0xc1, 0x98, 0xa7, 0xe5, 0xe5, 0x60, 0xfb, 0x5d, 0x68, 0xe5, 0x29, 0x72, 0x8f, 0x7d,
0x60, 0x6b, 0xe9, 0x97, 0xe4, 0xf5, 0xdd, 0x66, 0x79, 0x39, 0x2c, 0x5d, 0x59, 0x0e, 0xc9, 0xbb, 0x2d, 0xf7, 0x58, 0xcb, 0xfe, 0xd6, 0x82, 0xad, 0x95, 0xdf, 0x98, 0x37, 0x77, 0x9b, 0xd5, 0xe5,
0x40, 0xf2, 0x24, 0x4e, 0x7e, 0xca, 0xee, 0xe4, 0x08, 0xf5, 0xb6, 0x99, 0x6f, 0x5f, 0x95, 0x57, 0xb0, 0x70, 0x69, 0x39, 0x24, 0xef, 0x02, 0xc9, 0x92, 0x38, 0xd9, 0x29, 0xbb, 0x9d, 0x21, 0xd4,
0x6a, 0x5f, 0x02, 0x80, 0xb2, 0x17, 0x66, 0xd4, 0xcb, 0xb7, 0x75, 0xab, 0xd8, 0xd6, 0x0f, 0xa0, 0xdb, 0x66, 0xb6, 0x7d, 0x95, 0x5e, 0xa9, 0x7d, 0x09, 0x00, 0xca, 0x5e, 0x9a, 0x51, 0x2f, 0xdb,
0x69, 0xfe, 0x15, 0x72, 0xaa, 0x7a, 0x4f, 0x09, 0xed, 0xfc, 0x3f, 0xd9, 0x25, 0xbd, 0xec, 0x9f, 0xd6, 0xad, 0x7c, 0x5b, 0xdf, 0x87, 0xba, 0xf9, 0x27, 0xc9, 0x89, 0xea, 0x3d, 0x05, 0xb4, 0xf3,
0x27, 0x87, 0xe6, 0x7f, 0x27, 0x46, 0xe8, 0x9e, 0x62, 0xa0, 0x79, 0x6e, 0xfb, 0xb7, 0x16, 0xb4, 0xff, 0x2d, 0x2f, 0xe9, 0x2e, 0xff, 0xad, 0x72, 0x60, 0xfe, 0xab, 0x62, 0x84, 0xee, 0x29, 0x06,
0x95, 0x56, 0xb9, 0x9b, 0xbf, 0x07, 0xcd, 0x64, 0x01, 0xa5, 0x5d, 0x60, 0x3b, 0x93, 0x9f, 0x91, 0x9a, 0xe5, 0xb6, 0x7f, 0x67, 0x41, 0x4b, 0x69, 0x95, 0xb9, 0xf9, 0x07, 0x50, 0x4f, 0x16, 0x50,
0xd2, 0x3c, 0x21, 0x79, 0x04, 0xdb, 0x62, 0x76, 0x96, 0x76, 0x92, 0x2f, 0x44, 0x14, 0x3e, 0x9e, 0xda, 0x05, 0xb6, 0x97, 0xf2, 0x97, 0xa4, 0x34, 0x4b, 0x48, 0x1e, 0xc3, 0xb6, 0x98, 0x9d, 0xa6,
0x4b, 0x9e, 0xf6, 0xd7, 0x95, 0xdf, 0xc8, 0xbb, 0x70, 0x23, 0x5d, 0xba, 0x32, 0x06, 0xbd, 0x89, 0x9d, 0xe4, 0x0b, 0x11, 0x85, 0x4f, 0xe6, 0x92, 0xa7, 0xfd, 0x75, 0xed, 0x37, 0xf2, 0x2e, 0xdc,
0x5e, 0xfd, 0x60, 0xff, 0xc6, 0x82, 0xa6, 0x52, 0xf6, 0x40, 0xff, 0xcc, 0x8c, 0xd3, 0xde, 0xc2, 0x4c, 0x97, 0xae, 0x25, 0x83, 0xde, 0x44, 0x2f, 0x7f, 0xb0, 0x7f, 0x6b, 0x41, 0x5d, 0x29, 0xbb,
0xa3, 0xea, 0xb8, 0xb2, 0xad, 0xdc, 0x82, 0xaa, 0xf9, 0xf9, 0xc6, 0x34, 0x74, 0xf3, 0xeb, 0x4d, 0xaf, 0x7f, 0x80, 0xc6, 0x69, 0x6f, 0xe1, 0x51, 0x75, 0x5c, 0xdb, 0x56, 0x6e, 0x43, 0xd9, 0xfc,
0x2e, 0x26, 0x2a, 0x85, 0x98, 0xd8, 0x85, 0x86, 0x69, 0x53, 0x5c, 0x74, 0x37, 0x70, 0x06, 0xcd, 0x7c, 0x63, 0x1a, 0xba, 0xf9, 0xf5, 0x26, 0x13, 0x13, 0xa5, 0x5c, 0x4c, 0xec, 0x42, 0xcd, 0xb4,
0x10, 0x59, 0x7a, 0x54, 0xf3, 0x53, 0xce, 0x9f, 0xcc, 0xec, 0x61, 0x54, 0x53, 0xe3, 0x6e, 0x14, 0x29, 0x2e, 0x3a, 0x37, 0x70, 0x06, 0x5d, 0x22, 0x96, 0xe9, 0x51, 0xce, 0x4e, 0x39, 0x7f, 0x36,
0x92, 0x0f, 0xa1, 0xca, 0xf0, 0x84, 0x3a, 0xb6, 0x1f, 0xd9, 0xc5, 0x50, 0x28, 0x10, 0xef, 0xe9, 0xb3, 0x87, 0x51, 0x4d, 0x8d, 0xbb, 0x51, 0x48, 0x3e, 0x84, 0x32, 0xc3, 0x13, 0xea, 0xd8, 0x7a,
0x3f, 0xd4, 0x70, 0x90, 0xb7, 0x60, 0x33, 0x0a, 0x3c, 0x43, 0x32, 0x5e, 0x94, 0xf7, 0x22, 0x92, 0x6c, 0xe7, 0x43, 0x21, 0x47, 0xbc, 0xa7, 0xff, 0x50, 0xc3, 0x41, 0xde, 0x82, 0x66, 0x14, 0x78,
0xbc, 0x87, 0x8f, 0x50, 0x90, 0xd9, 0x6a, 0x5e, 0x5f, 0x79, 0x05, 0x4d, 0xa9, 0x54, 0xbb, 0xab, 0x86, 0x64, 0xbc, 0x28, 0xef, 0x79, 0x24, 0x79, 0x0f, 0x1f, 0xa1, 0x20, 0xb3, 0xd5, 0xbc, 0xbe,
0x1a, 0xed, 0x6e, 0xc0, 0xe6, 0xc1, 0xf0, 0xab, 0x7e, 0x8f, 0x0e, 0x9c, 0xde, 0x60, 0x80, 0x99, 0xf6, 0x0a, 0x9a, 0x52, 0xa9, 0x76, 0x57, 0x36, 0xda, 0xdd, 0x84, 0xe6, 0xfe, 0xe0, 0xab, 0x5e,
0x44, 0xa0, 0xdd, 0xeb, 0xf7, 0x8f, 0xc6, 0xa3, 0xd3, 0x13, 0x83, 0xb3, 0xc8, 0x4d, 0xd8, 0x4a, 0x97, 0xf6, 0x9d, 0x6e, 0xbf, 0x8f, 0x99, 0x44, 0xa0, 0xd5, 0xed, 0xf5, 0x0e, 0xc7, 0xa3, 0x93,
0xc9, 0x06, 0xc3, 0xa7, 0x43, 0x5d, 0x5f, 0xb6, 0xa1, 0xb3, 0x20, 0xa4, 0xc3, 0xc3, 0xa3, 0x67, 0x63, 0x83, 0xb3, 0xc8, 0x2d, 0xd8, 0x4a, 0xc9, 0xfa, 0x83, 0x67, 0x03, 0x5d, 0x5f, 0xb6, 0xa1,
0x58, 0x67, 0x00, 0xaa, 0x4f, 0x8f, 0xfa, 0x07, 0xaa, 0xca, 0xa8, 0xa4, 0x1c, 0x8f, 0x0c, 0xb4, 0xbd, 0x20, 0xa4, 0x83, 0x83, 0xc3, 0xe7, 0x58, 0x67, 0x00, 0xca, 0xcf, 0x0e, 0x7b, 0xfb, 0xaa,
0x41, 0xb6, 0xa0, 0x39, 0xde, 0x1f, 0x38, 0xe3, 0xe3, 0x41, 0x4f, 0x09, 0xa8, 0x92, 0x0e, 0xb4, 0xca, 0xa8, 0xa4, 0x1c, 0x8f, 0x0c, 0x74, 0x83, 0x6c, 0x41, 0x7d, 0x3c, 0xec, 0x3b, 0xe3, 0xa3,
0x46, 0xbd, 0xc3, 0xa1, 0xd3, 0x7f, 0xd2, 0x1b, 0x7d, 0x3e, 0x1c, 0x74, 0x6a, 0xf6, 0xd7, 0xba, 0x7e, 0x57, 0x09, 0x28, 0x93, 0x36, 0x34, 0x46, 0xdd, 0x83, 0x81, 0xd3, 0x7b, 0xda, 0x1d, 0x7d,
0xdb, 0xf5, 0x82, 0xc0, 0x28, 0x2d, 0xc8, 0xff, 0x43, 0xdd, 0xe8, 0x9d, 0xc6, 0xe1, 0x9a, 0xe7, 0x3e, 0xe8, 0xb7, 0x2b, 0xf6, 0xd7, 0xba, 0xdb, 0x75, 0x83, 0xc0, 0x28, 0x2d, 0xc8, 0x77, 0xa1,
0x2d, 0xc8, 0x32, 0xf7, 0x94, 0x72, 0xee, 0x79, 0xbc, 0xf9, 0x75, 0x73, 0xef, 0xbd, 0x8f, 0x52, 0x6a, 0xf4, 0x4e, 0xe3, 0x70, 0xc3, 0xf3, 0x16, 0x64, 0x4b, 0xf7, 0x14, 0x32, 0xee, 0x79, 0xd2,
0xd6, 0xb3, 0x2a, 0x9e, 0xde, 0xff, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0xde, 0xb0, 0x84, 0xfc, 0xba, 0xbe, 0xf7, 0xde, 0x47, 0x29, 0xeb, 0x69, 0x19, 0x4f, 0xef, 0xff, 0x3b, 0x00, 0x00,
0x97, 0x1c, 0x00, 0x00, 0xff, 0xff, 0x48, 0xc1, 0x44, 0x9e, 0xb1, 0x1c, 0x00, 0x00,
} }

View File

@ -70,6 +70,8 @@ message PairInstallation {
string installation_id = 2; string installation_id = 2;
string device_type = 3; string device_type = 3;
string name = 4; string name = 4;
// following fields used for local pairing
uint32 version = 5;
} }
message SyncInstallationContact { message SyncInstallationContact {

View File

@ -24,6 +24,7 @@ type Client struct {
*http.Client *http.Client
PayloadManager PayloadManager
rawMessagePayloadManager *RawMessagePayloadManager rawMessagePayloadManager *RawMessagePayloadManager
installationPayloadManager *InstallationPayloadManager
baseAddress *url.URL baseAddress *url.URL
certPEM []byte certPEM []byte
@ -70,12 +71,18 @@ func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, confi
return nil, err return nil, err
} }
pm, err := NewAccountPayloadManager(c.aesKey, config, logutils.ZapLogger().Named("Client")) logger := logutils.ZapLogger().Named("Client")
pm, err := NewAccountPayloadManager(c.aesKey, config, logger)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rmpm, err := NewRawMessagePayloadManager(logutils.ZapLogger().Named("Client"), pm.accountPayload, c.aesKey, backend, config.GetNodeConfig(), config.GetSettingCurrentNetwork()) rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, c.aesKey, backend, config.GetNodeConfig(), config.GetSettingCurrentNetwork(), config.GetDeviceType())
if err != nil {
return nil, err
}
ipm, err := NewInstallationPayloadManager(logger, c.aesKey, backend, config.GetDeviceType())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,6 +95,7 @@ func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, confi
serverMode: c.serverMode, serverMode: c.serverMode,
PayloadManager: pm, PayloadManager: pm,
rawMessagePayloadManager: rmpm, rawMessagePayloadManager: rmpm,
installationPayloadManager: ipm,
}, nil }, nil
} }
@ -117,6 +125,55 @@ func (c *Client) PairSyncDevice() error {
} }
} }
// PairInstallation transfer installation data from receiver to sender.
// installation data from sender to receiver already processed within PairSyncDevice method on the receiver side.
func (c *Client) PairInstallation() error {
switch c.serverMode {
case Receiving:
return c.receiveInstallationData()
case Sending:
return c.sendInstallationData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *Client) sendInstallationData() error {
err := c.installationPayloadManager.Mount()
if err != nil {
return err
}
c.baseAddress.Path = pairingReceiveInstallation
req, err := http.NewRequest(http.MethodPost, c.baseAddress.String(), bytes.NewBuffer(c.installationPayloadManager.ToSend()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/octet-stream")
if c.serverChallenge != nil {
ec, err := c.PayloadManager.EncryptPlain(c.serverChallenge)
if err != nil {
return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
}
resp, err := c.Do(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not okay when sending installation data, status: %s", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
return nil
}
func (c *Client) sendSyncDeviceData() error { func (c *Client) sendSyncDeviceData() error {
err := c.rawMessagePayloadManager.Mount() err := c.rawMessagePayloadManager.Mount()
if err != nil { if err != nil {
@ -131,14 +188,50 @@ func (c *Client) sendSyncDeviceData() error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not okay when sending sync device data, status: %s", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return fmt.Errorf("status not ok, received '%s'", resp.Status) return err
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
return nil return nil
} }
func (c *Client) receiveInstallationData() error {
c.baseAddress.Path = pairingSendInstallation
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil {
return err
}
resp, err := c.Do(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not ok when receiving installation data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
payload, err := io.ReadAll(resp.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
err = c.installationPayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingInstallation})
return nil
}
func (c *Client) receiveSyncDeviceData() error { func (c *Client) receiveSyncDeviceData() error {
c.baseAddress.Path = pairingSyncDeviceSend c.baseAddress.Path = pairingSyncDeviceSend
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil) req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
@ -162,7 +255,7 @@ func (c *Client) receiveSyncDeviceData() error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("status not ok, received '%s'", resp.Status) err = fmt.Errorf("[client] status not ok when receiving sync device data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err return err
} }
@ -197,8 +290,9 @@ func (c *Client) sendAccountData() error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not ok when sending account data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return fmt.Errorf("status not ok, received '%s'", resp.Status) return err
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
@ -230,12 +324,12 @@ func (c *Client) receiveAccountData() error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("status not ok, received '%s'", resp.Status) err = fmt.Errorf("[client] status not ok when receiving account data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
} }
payload, err := ioutil.ReadAll(resp.Body) payload, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
@ -259,7 +353,7 @@ func (c *Client) getChallenge() error {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status) return fmt.Errorf("[client] status not ok when getting challenge, received '%s'", resp.Status)
} }
c.serverChallenge, err = ioutil.ReadAll(resp.Body) c.serverChallenge, err = ioutil.ReadAll(resp.Body)
@ -275,7 +369,11 @@ func StartUpPairingClient(backend *api.GethStatusBackend, cs, configJSON string)
if err != nil { if err != nil {
return err return err
} }
return c.PairSyncDevice() err = c.PairSyncDevice()
if err != nil {
return err
}
return c.PairInstallation()
} }
func setupClient(backend *api.GethStatusBackend, cs string, configJSON string) (*Client, error) { func setupClient(backend *api.GethStatusBackend, cs string, configJSON string) (*Client, error) {
@ -290,7 +388,11 @@ func setupClient(backend *api.GethStatusBackend, cs string, configJSON string) (
return nil, err return nil, err
} }
c, err := NewPairingClient(backend, ccp, &AccountPayloadManagerConfig{backend.GetMultiaccountDB(), conf}) accountPayloadManagerConfig := &AccountPayloadManagerConfig{DB: backend.GetMultiaccountDB(), PayloadSourceConfig: conf}
if ccp.serverMode == Sending {
updateLoggedInKeyUID(accountPayloadManagerConfig, backend)
}
c, err := NewPairingClient(backend, ccp, accountPayloadManagerConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }

10
server/pairing/common.go Normal file
View File

@ -0,0 +1,10 @@
package pairing
import "github.com/status-im/status-go/api"
func updateLoggedInKeyUID(accountPayloadManagerConfig *AccountPayloadManagerConfig, backend *api.GethStatusBackend) {
activeAccount, _ := backend.GetActiveAccount()
if activeAccount != nil {
accountPayloadManagerConfig.LoggedInKeyUID = activeAccount.KeyUID
}
}

View File

@ -32,4 +32,5 @@ const (
ActionConnect = iota + 1 ActionConnect = iota + 1
ActionPairingAccount ActionPairingAccount
ActionSyncDevice ActionSyncDevice
ActionPairingInstallation
) )

View File

@ -21,20 +21,22 @@ const (
pairingChallenge = pairingBase + "/challenge" pairingChallenge = pairingBase + "/challenge"
pairingSyncDeviceSend = pairingBase + "/sendSyncDevice" pairingSyncDeviceSend = pairingBase + "/sendSyncDevice"
pairingSyncDeviceReceive = pairingBase + "/receiveSyncDevice" pairingSyncDeviceReceive = pairingBase + "/receiveSyncDevice"
pairingSendInstallation = pairingBase + "/sendInstallation"
pairingReceiveInstallation = pairingBase + "/receiveInstallation"
// Session names // Session names
sessionChallenge = "challenge" sessionChallenge = "challenge"
sessionBlocked = "blocked" sessionBlocked = "blocked"
) )
func handlePairingReceive(ps *Server) http.HandlerFunc { func handleReceiveAccount(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger() logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body) payload, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("ioutil.ReadAll(r.Body)", zap.Error(err)) logger.Error("handleReceiveAccount io.ReadAll(r.Body)", zap.Error(err))
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
@ -49,6 +51,28 @@ func handlePairingReceive(ps *Server) http.HandlerFunc {
} }
} }
func handleReceiveInstallation(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("handleReceiveInstallation io.ReadAll(r.Body)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
err = ps.installationPayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("ps.installationPayloadManager.Receive(payload)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingInstallation})
}
}
func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc { func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
logger := ps.GetLogger() logger := ps.GetLogger()
@ -56,7 +80,7 @@ func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc {
payload, err := io.ReadAll(r.Body) payload, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("io.ReadAll(r.Body)", zap.Error(err)) logger.Error("handleParingSyncDeviceReceive io.ReadAll(r.Body)", zap.Error(err))
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
@ -71,7 +95,7 @@ func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc {
} }
} }
func handlePairingSend(ps *Server) http.HandlerFunc { func handleSendAccount(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger() logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
@ -88,6 +112,30 @@ func handlePairingSend(ps *Server) http.HandlerFunc {
} }
} }
func handleSendInstallation(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
err := ps.installationPayloadManager.Mount()
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("ps.installationPayloadManager.Mount()", zap.Error(err))
return
}
_, err = w.Write(ps.installationPayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("w.Write(ps.installationPayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
ps.installationPayloadManager.LockPayload()
}
}
func handlePairingSyncDeviceSend(ps *Server) http.HandlerFunc { func handlePairingSyncDeviceSend(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
logger := ps.GetLogger() logger := ps.GetLogger()

View File

@ -27,6 +27,7 @@ var (
ErrKeyUIDEmptyAsSender = errors.New("keyUID must be provided as sender") ErrKeyUIDEmptyAsSender = errors.New("keyUID must be provided as sender")
ErrNodeConfigNilAsReceiver = errors.New("node config must be provided as receiver") ErrNodeConfigNilAsReceiver = errors.New("node config must be provided as receiver")
ErrPayloadSourceConfigBothSet = errors.New("payloadSourceSenderConfig and payloadSourceReceiverConfig cannot be both set") ErrPayloadSourceConfigBothSet = errors.New("payloadSourceSenderConfig and payloadSourceReceiverConfig cannot be both set")
ErrLoggedInKeyUIDConflict = errors.New("logged in keyUID not same as keyUID in payload")
) )
// PayloadManager is the interface for PayloadManagers and wraps the basic functions for fulfilling payload management // PayloadManager is the interface for PayloadManagers and wraps the basic functions for fulfilling payload management
@ -74,6 +75,8 @@ type PayloadSourceConfig struct {
// 1. for sender, KeystorePath must end with keyUID // 1. for sender, KeystorePath must end with keyUID
// 2. for receiver, KeystorePath must not end with keyUID (because keyUID is not known yet) // 2. for receiver, KeystorePath must not end with keyUID (because keyUID is not known yet)
KeystorePath string `json:"keystorePath"` KeystorePath string `json:"keystorePath"`
// required for sender and receiver, SendPairInstallation need this information
DeviceType string `json:"deviceType"`
*PayloadSourceSenderConfig *PayloadSourceSenderConfig
*PayloadSourceReceiverConfig *PayloadSourceReceiverConfig
// Timeout the number of milliseconds after which the pairing server will automatically terminate // Timeout the number of milliseconds after which the pairing server will automatically terminate
@ -126,6 +129,8 @@ func unmarshalPayloadSourceConfig(configJSON string, successCallback payloadSour
type AccountPayloadManagerConfig struct { type AccountPayloadManagerConfig struct {
DB *multiaccounts.Database DB *multiaccounts.Database
*PayloadSourceConfig *PayloadSourceConfig
// only used for the receiver side
LoggedInKeyUID string
} }
func (a *AccountPayloadManagerConfig) GetNodeConfig() *params.NodeConfig { func (a *AccountPayloadManagerConfig) GetNodeConfig() *params.NodeConfig {
@ -142,6 +147,13 @@ func (a *AccountPayloadManagerConfig) GetSettingCurrentNetwork() string {
return "" return ""
} }
func (a *AccountPayloadManagerConfig) GetDeviceType() string {
if a.PayloadSourceConfig != nil {
return a.DeviceType
}
return ""
}
func (a *AccountPayloadManagerConfig) GetPayloadSourceSenderConfig() *PayloadSourceSenderConfig { func (a *AccountPayloadManagerConfig) GetPayloadSourceSenderConfig() *PayloadSourceSenderConfig {
if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceSenderConfig != nil { if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceSenderConfig != nil {
return a.PayloadSourceSenderConfig return a.PayloadSourceSenderConfig
@ -372,6 +384,8 @@ type AccountPayload struct {
keys map[string][]byte keys map[string][]byte
multiaccount *multiaccounts.Account multiaccount *multiaccounts.Account
password string password string
//flag if account already exist before sync account
exist bool
} }
func (ap *AccountPayload) ResetPayload() { func (ap *AccountPayload) ResetPayload() {
@ -463,6 +477,8 @@ type AccountPayloadRepository struct {
keystorePath, keyUID string keystorePath, keyUID string
kdfIterations int kdfIterations int
loggedInKeyUID string
} }
func NewAccountPayloadRepository(p *AccountPayload, config *AccountPayloadManagerConfig) (*AccountPayloadRepository, error) { func NewAccountPayloadRepository(p *AccountPayload, config *AccountPayloadManagerConfig) (*AccountPayloadRepository, error) {
@ -486,6 +502,7 @@ func NewAccountPayloadRepository(p *AccountPayload, config *AccountPayloadManage
ppr.kdfIterations = config.KDFIterations ppr.kdfIterations = config.KDFIterations
} }
ppr.keystorePath = config.GetKeystorePath() ppr.keystorePath = config.GetKeystorePath()
ppr.loggedInKeyUID = config.LoggedInKeyUID
return ppr, nil return ppr, nil
} }
@ -548,16 +565,33 @@ func (apr *AccountPayloadRepository) loadKeys(keyStorePath string) error {
} }
func (apr *AccountPayloadRepository) StoreToSource() error { func (apr *AccountPayloadRepository) StoreToSource() error {
keyUID := apr.multiaccount.KeyUID
if apr.loggedInKeyUID != "" && apr.loggedInKeyUID != keyUID {
return ErrLoggedInKeyUIDConflict
}
if apr.loggedInKeyUID == keyUID {
// skip storing keys if user is logged in with the same key
return nil
}
err := apr.validateKeys(apr.password) err := apr.validateKeys(apr.password)
if err != nil { if err != nil {
return err return err
} }
err = apr.storeKeys(apr.keystorePath) if err = apr.storeKeys(apr.keystorePath); err != nil && err != ErrKeyFileAlreadyExists {
if err != nil {
return err return err
} }
// skip storing multiaccount if key already exists
if err == ErrKeyFileAlreadyExists {
apr.exist = true
apr.multiaccount, err = apr.multiaccountsDB.GetAccount(keyUID)
if err != nil {
return err
}
return nil
}
err = apr.storeMultiAccount() err = apr.storeMultiAccount()
if err != nil { if err != nil {
return err return err
@ -641,7 +675,7 @@ type RawMessagePayloadManager struct {
payloadRepository *RawMessageRepository payloadRepository *RawMessageRepository
} }
func NewRawMessagePayloadManager(logger *zap.Logger, accountPayload *AccountPayload, aesKey []byte, backend *api.GethStatusBackend, nodeConfig *params.NodeConfig, settingCurrentNetwork string) (*RawMessagePayloadManager, error) { func NewRawMessagePayloadManager(logger *zap.Logger, accountPayload *AccountPayload, aesKey []byte, backend *api.GethStatusBackend, nodeConfig *params.NodeConfig, settingCurrentNetwork, deviceType string) (*RawMessagePayloadManager, error) {
l := logger.Named("RawMessagePayloadManager") l := logger.Named("RawMessagePayloadManager")
pem, err := NewPayloadEncryptionManager(aesKey, l) pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil { if err != nil {
@ -651,7 +685,7 @@ func NewRawMessagePayloadManager(logger *zap.Logger, accountPayload *AccountPayl
logger: l, logger: l,
accountPayload: accountPayload, accountPayload: accountPayload,
PayloadEncryptionManager: pem, PayloadEncryptionManager: pem,
payloadRepository: NewRawMessageRepository(backend, accountPayload, nodeConfig, settingCurrentNetwork), payloadRepository: NewRawMessageRepository(backend, accountPayload, nodeConfig, settingCurrentNetwork, deviceType),
}, nil }, nil
} }
@ -683,15 +717,17 @@ type RawMessageRepository struct {
accountPayload *AccountPayload accountPayload *AccountPayload
nodeConfig *params.NodeConfig nodeConfig *params.NodeConfig
settingCurrentNetwork string settingCurrentNetwork string
deviceType string
} }
func NewRawMessageRepository(backend *api.GethStatusBackend, accountPayload *AccountPayload, config *params.NodeConfig, settingCurrentNetwork string) *RawMessageRepository { func NewRawMessageRepository(backend *api.GethStatusBackend, accountPayload *AccountPayload, config *params.NodeConfig, settingCurrentNetwork, deviceType string) *RawMessageRepository {
return &RawMessageRepository{ return &RawMessageRepository{
syncRawMessageHandler: NewSyncRawMessageHandler(backend), syncRawMessageHandler: NewSyncRawMessageHandler(backend),
payload: make([]byte, 0), payload: make([]byte, 0),
accountPayload: accountPayload, accountPayload: accountPayload,
nodeConfig: config, nodeConfig: config,
settingCurrentNetwork: settingCurrentNetwork, settingCurrentNetwork: settingCurrentNetwork,
deviceType: deviceType,
} }
} }
@ -700,7 +736,7 @@ func (r *RawMessageRepository) LoadFromSource() error {
if account == nil || account.KeyUID == "" { if account == nil || account.KeyUID == "" {
return fmt.Errorf("no known KeyUID when loading raw messages") return fmt.Errorf("no known KeyUID when loading raw messages")
} }
payload, err := r.syncRawMessageHandler.PrepareRawMessage(account.KeyUID) payload, err := r.syncRawMessageHandler.PrepareRawMessage(account.KeyUID, r.deviceType)
if err != nil { if err != nil {
return err return err
} }
@ -713,5 +749,87 @@ func (r *RawMessageRepository) StoreToSource() error {
if accountPayload == nil || accountPayload.multiaccount == nil { if accountPayload == nil || accountPayload.multiaccount == nil {
return fmt.Errorf("no known multiaccount when storing raw messages") return fmt.Errorf("no known multiaccount when storing raw messages")
} }
return r.syncRawMessageHandler.HandleRawMessage(accountPayload.multiaccount, accountPayload.password, r.nodeConfig, r.settingCurrentNetwork, r.payload) return r.syncRawMessageHandler.HandleRawMessage(accountPayload, r.nodeConfig, r.settingCurrentNetwork, r.deviceType, r.payload)
}
type InstallationPayloadManager struct {
logger *zap.Logger
*PayloadEncryptionManager
installationPayloadRepository *InstallationPayloadRepository
}
func NewInstallationPayloadManager(logger *zap.Logger, aesKey []byte, backend *api.GethStatusBackend, deviceType string) (*InstallationPayloadManager, error) {
l := logger.Named("InstallationPayloadManager")
pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil {
return nil, err
}
return &InstallationPayloadManager{
logger: l,
PayloadEncryptionManager: pem,
installationPayloadRepository: NewInstallationPayloadRepository(backend, deviceType),
}, nil
}
func (i *InstallationPayloadManager) Mount() error {
err := i.installationPayloadRepository.LoadFromSource()
if err != nil {
return err
}
return i.Encrypt(i.installationPayloadRepository.payload)
}
func (i *InstallationPayloadManager) Receive(data []byte) error {
err := i.Decrypt(data)
if err != nil {
return err
}
i.installationPayloadRepository.payload = i.Received()
return i.installationPayloadRepository.StoreToSource()
}
func (i *InstallationPayloadManager) ResetPayload() {
i.installationPayloadRepository.payload = make([]byte, 0)
i.PayloadEncryptionManager.ResetPayload()
}
type InstallationPayloadRepository struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
deviceType string
backend *api.GethStatusBackend
}
func NewInstallationPayloadRepository(backend *api.GethStatusBackend, deviceType string) *InstallationPayloadRepository {
return &InstallationPayloadRepository{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
deviceType: deviceType,
backend: backend,
}
}
func (r *InstallationPayloadRepository) LoadFromSource() error {
rawMessageCollector := new(RawMessageCollector)
err := r.syncRawMessageHandler.CollectInstallationData(rawMessageCollector, r.deviceType)
if err != nil {
return err
}
r.payload, err = proto.Marshal(rawMessageCollector.convertToSyncRawMessage())
return err
}
func (r *InstallationPayloadRepository) StoreToSource() error {
messenger := r.backend.Messenger()
if messenger == nil {
return fmt.Errorf("messenger is nil when invoke InstallationPayloadRepository#StoreToSource")
}
rawMessages, _, _, err := r.syncRawMessageHandler.unmarshalSyncRawMessage(r.payload)
if err != nil {
return err
}
err = messenger.SetInstallationDeviceType(r.deviceType)
if err != nil {
return err
}
return messenger.HandleSyncRawMessages(rawMessages)
} }

View File

@ -335,7 +335,7 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
pms.Require().Exactly(expected.Timestamp, acc.Timestamp) pms.Require().Exactly(expected.Timestamp, acc.Timestamp)
pms.Require().Len(acc.Images, 2) pms.Require().Len(acc.Images, 2)
err = ppr2.StoreToSource() err = ppr2.storeKeys(ppr2.keystorePath)
pms.Require().ErrorIs(err, ErrKeyFileAlreadyExists) pms.Require().ErrorIs(err, ErrKeyFileAlreadyExists)
} }

View File

@ -3,6 +3,8 @@ package pairing
import ( import (
"context" "context"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
) )
@ -18,3 +20,14 @@ func (r *RawMessageCollector) dispatchMessage(_ context.Context, rawMessage comm
func (r *RawMessageCollector) getRawMessages() []*common.RawMessage { func (r *RawMessageCollector) getRawMessages() []*common.RawMessage {
return r.rawMessages return r.rawMessages
} }
func (r *RawMessageCollector) convertToSyncRawMessage() *protobuf.SyncRawMessage {
syncRawMessage := new(protobuf.SyncRawMessage)
for _, m := range r.getRawMessages() {
rawMessage := new(protobuf.RawMessage)
rawMessage.Payload = m.Payload
rawMessage.MessageType = m.MessageType
syncRawMessage.RawMessages = append(syncRawMessage.RawMessages, rawMessage)
}
return syncRawMessage
}

View File

@ -9,7 +9,6 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/status-im/status-go/api" "github.com/status-im/status-go/api"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
@ -24,7 +23,20 @@ func NewSyncRawMessageHandler(backend *api.GethStatusBackend) *SyncRawMessageHan
return &SyncRawMessageHandler{backend: backend} return &SyncRawMessageHandler{backend: backend}
} }
func (s *SyncRawMessageHandler) PrepareRawMessage(keyUID string) ([]byte, error) { func (s *SyncRawMessageHandler) CollectInstallationData(rawMessageCollector *RawMessageCollector, deviceType string) error {
messenger := s.backend.Messenger()
if messenger == nil {
return fmt.Errorf("messenger is nil when CollectInstallationData")
}
err := messenger.SetInstallationDeviceType(deviceType)
if err != nil {
return err
}
_, err = messenger.SendPairInstallation(context.TODO(), rawMessageCollector.dispatchMessage)
return err
}
func (s *SyncRawMessageHandler) PrepareRawMessage(keyUID, deviceType string) ([]byte, error) {
messenger := s.backend.Messenger() messenger := s.backend.Messenger()
if messenger == nil { if messenger == nil {
return nil, fmt.Errorf("messenger is nil when PrepareRawMessage") return nil, fmt.Errorf("messenger is nil when PrepareRawMessage")
@ -47,14 +59,14 @@ func (s *SyncRawMessageHandler) PrepareRawMessage(keyUID string) ([]byte, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
syncRawMessage := new(protobuf.SyncRawMessage)
for _, m := range rawMessageCollector.getRawMessages() { err = s.CollectInstallationData(rawMessageCollector, deviceType)
rawMessage := new(protobuf.RawMessage) if err != nil {
rawMessage.Payload = m.Payload return nil, err
rawMessage.MessageType = m.MessageType
syncRawMessage.RawMessages = append(syncRawMessage.RawMessages, rawMessage)
} }
syncRawMessage := rawMessageCollector.convertToSyncRawMessage()
accountService := s.backend.StatusNode().AccountService() accountService := s.backend.StatusNode().AccountService()
var ( var (
subAccounts []*accounts.Account subAccounts []*accounts.Account
@ -80,33 +92,45 @@ func (s *SyncRawMessageHandler) PrepareRawMessage(keyUID string) ([]byte, error)
return proto.Marshal(syncRawMessage) return proto.Marshal(syncRawMessage)
} }
func (s *SyncRawMessageHandler) HandleRawMessage(account *multiaccounts.Account, password string, nodeConfig *params.NodeConfig, settingCurrentNetwork string, payload []byte) error { func (s *SyncRawMessageHandler) HandleRawMessage(accountPayload *AccountPayload, nodeConfig *params.NodeConfig, settingCurrentNetwork, deviceType string, rawMessagePayload []byte) error {
rawMessages, subAccounts, setting, err := s.unmarshalSyncRawMessage(payload) account := accountPayload.multiaccount
rawMessages, subAccounts, setting, err := s.unmarshalSyncRawMessage(rawMessagePayload)
if err != nil { if err != nil {
return err return err
} }
activeAccount, _ := s.backend.GetActiveAccount()
if activeAccount == nil { // not login yet
s.backend.UpdateRootDataDir(nodeConfig.RootDataDir) s.backend.UpdateRootDataDir(nodeConfig.RootDataDir)
// because client don't know keyUID before received data, we need help client to update keystore dir // because client don't know keyUID before received data, we need help client to update keystore dir
newKeystoreDir := filepath.Join(nodeConfig.KeyStoreDir, account.KeyUID) keystoreDir := filepath.Join(nodeConfig.KeyStoreDir, account.KeyUID)
nodeConfig.KeyStoreDir = newKeystoreDir nodeConfig.KeyStoreDir = keystoreDir
if accountPayload.exist {
err = s.backend.StartNodeWithAccount(*account, accountPayload.password, nodeConfig)
} else {
accountManager := s.backend.AccountManager() accountManager := s.backend.AccountManager()
err = accountManager.InitKeystore(filepath.Join(nodeConfig.RootDataDir, newKeystoreDir)) err = accountManager.InitKeystore(filepath.Join(nodeConfig.RootDataDir, keystoreDir))
if err != nil { if err != nil {
return err return err
} }
setting.InstallationID = nodeConfig.ShhextConfig.InstallationID setting.InstallationID = nodeConfig.ShhextConfig.InstallationID
setting.CurrentNetwork = settingCurrentNetwork setting.CurrentNetwork = settingCurrentNetwork
err = s.backend.StartNodeWithAccountAndInitialConfig(*account, password, *setting, nodeConfig, subAccounts) err = s.backend.StartNodeWithAccountAndInitialConfig(*account, accountPayload.password, *setting, nodeConfig, subAccounts)
}
if err != nil { if err != nil {
return err return err
} }
}
messenger := s.backend.Messenger() messenger := s.backend.Messenger()
if messenger == nil { if messenger == nil {
return fmt.Errorf("messenger is nil when HandleRawMessage") return fmt.Errorf("messenger is nil when HandleRawMessage")
} }
err = messenger.SetInstallationDeviceType(deviceType)
if err != nil {
return err
}
return messenger.HandleSyncRawMessages(rawMessages) return messenger.HandleSyncRawMessages(rawMessages)
} }
@ -120,13 +144,17 @@ func (s *SyncRawMessageHandler) unmarshalSyncRawMessage(payload []byte) ([]*prot
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
if syncRawMessage.SubAccountsJsonBytes != nil {
err = json.Unmarshal(syncRawMessage.SubAccountsJsonBytes, &subAccounts) err = json.Unmarshal(syncRawMessage.SubAccountsJsonBytes, &subAccounts)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
}
if syncRawMessage.SettingsJsonBytes != nil {
err = json.Unmarshal(syncRawMessage.SettingsJsonBytes, &setting) err = json.Unmarshal(syncRawMessage.SettingsJsonBytes, &setting)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
}
return syncRawMessage.RawMessages, subAccounts, setting, nil return syncRawMessage.RawMessages, subAccounts, setting, nil
} }

View File

@ -20,6 +20,7 @@ type Server struct {
server.Server server.Server
PayloadManager PayloadManager
rawMessagePayloadManager *RawMessagePayloadManager rawMessagePayloadManager *RawMessagePayloadManager
installationPayloadManager *InstallationPayloadManager
pk *ecdsa.PublicKey pk *ecdsa.PublicKey
ek []byte ek []byte
@ -70,11 +71,15 @@ func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server,
return nil, err return nil, err
} }
rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, config.EK, backend, accountPayloadManagerConfig.GetNodeConfig(), accountPayloadManagerConfig.GetSettingCurrentNetwork()) rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, config.EK, backend, accountPayloadManagerConfig.GetNodeConfig(), accountPayloadManagerConfig.GetSettingCurrentNetwork(), accountPayloadManagerConfig.GetDeviceType())
if err != nil { if err != nil {
return nil, err return nil, err
} }
ipm, err := NewInstallationPayloadManager(logger, config.EK, backend, accountPayloadManagerConfig.GetDeviceType())
if err != nil {
return nil, err
}
s := &Server{Server: server.NewServer( s := &Server{Server: server.NewServer(
config.Cert, config.Cert,
config.Hostname, config.Hostname,
@ -87,6 +92,7 @@ func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server,
PayloadManager: pm, PayloadManager: pm,
cookieStore: cs, cookieStore: cs,
rawMessagePayloadManager: rmpm, rawMessagePayloadManager: rmpm,
installationPayloadManager: ipm,
} }
s.SetTimeout(config.GetTimeout()) s.SetTimeout(config.GetTimeout())
@ -122,9 +128,11 @@ func (s *Server) StartPairing() error {
func (s *Server) startReceivingData() error { func (s *Server) startReceivingData() error {
s.SetHandlers(server.HandlerPatternMap{ s.SetHandlers(server.HandlerPatternMap{
pairingReceiveAccount: handlePairingReceive(s), pairingReceiveAccount: handleReceiveAccount(s),
pairingChallenge: handlePairingChallenge(s), pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceReceive: handleParingSyncDeviceReceive(s), pairingSyncDeviceReceive: handleParingSyncDeviceReceive(s),
// send installation data back to sender
pairingSendInstallation: handleSendInstallation(s),
}) })
return s.Start() return s.Start()
} }
@ -136,9 +144,11 @@ func (s *Server) startSendingData() error {
} }
s.SetHandlers(server.HandlerPatternMap{ s.SetHandlers(server.HandlerPatternMap{
pairingSendAccount: challengeMiddleware(s, handlePairingSend(s)), pairingSendAccount: challengeMiddleware(s, handleSendAccount(s)),
pairingChallenge: handlePairingChallenge(s), pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceSend: challengeMiddleware(s, handlePairingSyncDeviceSend(s)), pairingSyncDeviceSend: challengeMiddleware(s, handlePairingSyncDeviceSend(s)),
// receive installation data from receiver
pairingReceiveInstallation: challengeMiddleware(s, handleReceiveInstallation(s)),
}) })
return s.Start() return s.Start()
} }
@ -165,6 +175,18 @@ func MakeFullPairingServer(backend *api.GethStatusBackend, mode Mode, storeConfi
if err != nil { if err != nil {
return nil, err return nil, err
} }
accountPayloadManagerConfig := &AccountPayloadManagerConfig{
// Things that can't be generated, but DO NOT come from app client
DB: backend.GetMultiaccountDB(),
// Things that can't be generated, but DO come from the app client
PayloadSourceConfig: storeConfig,
}
if mode == Receiving {
updateLoggedInKeyUID(accountPayloadManagerConfig, backend)
}
return NewPairingServer(backend, &Config{ return NewPairingServer(backend, &Config{
// Things that can be generated, and CANNOT come from the app client (well they could be this is better) // Things that can be generated, and CANNOT come from the app client (well they could be this is better)
PK: &tlsKey.PublicKey, PK: &tlsKey.PublicKey,
@ -175,13 +197,7 @@ func MakeFullPairingServer(backend *api.GethStatusBackend, mode Mode, storeConfi
// Things that can't be generated, but DO come from the app client // Things that can't be generated, but DO come from the app client
Mode: mode, Mode: mode,
AccountPayloadManagerConfig: &AccountPayloadManagerConfig{ AccountPayloadManagerConfig: accountPayloadManagerConfig,
// Things that can't be generated, but DO NOT come from app client
DB: backend.GetMultiaccountDB(),
// Things that can't be generated, but DO come from the app client
PayloadSourceConfig: storeConfig,
},
}) })
} }

View File

@ -182,7 +182,7 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware(
// Attempt to get the private key data, this should fail because there is no challenge // Attempt to get the private key data, this should fail because there is no challenge
err := c.receiveAccountData() err := c.receiveAccountData()
s.Require().Error(err) s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error()) s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error())
err = c.getChallenge() err = c.getChallenge()
s.Require().NoError(err) s.Require().NoError(err)
@ -205,7 +205,7 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_
// Attempt to get the private key data, this should fail because there is no challenge // Attempt to get the private key data, this should fail because there is no challenge
err := c.receiveAccountData() err := c.receiveAccountData()
s.Require().Error(err) s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error()) s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error())
// Get the challenge // Get the challenge
err = c.getChallenge() err = c.getChallenge()
@ -220,7 +220,7 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_
// behind the scenes the server will block the session if the client fails the challenge. There is no forgiveness! // behind the scenes the server will block the session if the client fails the challenge. There is no forgiveness!
err = c.receiveAccountData() err = c.receiveAccountData()
s.Require().Error(err) s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error()) s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error())
// Get the real challenge // Get the real challenge
err = c.getChallenge() err = c.getChallenge()
@ -229,7 +229,7 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_
// Attempt to get the account data, should fail because the client is now blocked. // Attempt to get the account data, should fail because the client is now blocked.
err = c.receiveAccountData() err = c.receiveAccountData()
s.Require().Error(err) s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error()) s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error())
} }
func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) { func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {

View File

@ -113,6 +113,7 @@ func (s *SyncDeviceSuite) prepareBackendWithAccount(tmpdir string) *api.GethStat
accounts := []*accounts.Account{walletAccount, chatAccount} accounts := []*accounts.Account{walletAccount, chatAccount}
err = backend.StartNodeWithAccountAndInitialConfig(account, s.password, *settings, nodeConfig, accounts) err = backend.StartNodeWithAccountAndInitialConfig(account, s.password, *settings, nodeConfig, accounts)
require.NoError(s.T(), err) require.NoError(s.T(), err)
return backend return backend
} }
@ -125,11 +126,11 @@ func (s *SyncDeviceSuite) prepareBackendWithoutAccount(tmpdir string) *api.GethS
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() { func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
clientTmpDir := filepath.Join(s.clientAsSenderTmpdir, "client") clientTmpDir := filepath.Join(s.clientAsSenderTmpdir, "client")
clientBackend := s.prepareBackendWithAccount(clientTmpDir) clientBackend := s.prepareBackendWithAccount(clientTmpDir)
serverTmpDir := filepath.Join(s.clientAsSenderTmpdir, "server") serverTmpDir := filepath.Join(s.clientAsSenderTmpdir, "server")
serverBackend := s.prepareBackendWithoutAccount(serverTmpDir) serverBackend := s.prepareBackendWithoutAccount(serverTmpDir)
defer func() { defer func() {
require.NoError(s.T(), serverBackend.Logout()) require.NoError(s.T(), serverBackend.Logout())
require.NoError(s.T(), clientBackend.Logout())
}() }()
err := serverBackend.AccountManager().InitKeystore(filepath.Join(serverTmpDir, keystoreDir)) err := serverBackend.AccountManager().InitKeystore(filepath.Join(serverTmpDir, keystoreDir))
@ -142,6 +143,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
serverKeystoreDir := filepath.Join(serverTmpDir, keystoreDir) serverKeystoreDir := filepath.Join(serverTmpDir, keystoreDir)
serverPayloadSourceConfig := PayloadSourceConfig{ serverPayloadSourceConfig := PayloadSourceConfig{
KeystorePath: serverKeystoreDir, KeystorePath: serverKeystoreDir,
DeviceType: "desktop",
PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{ PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{
KDFIterations: expectedKDFIterations, KDFIterations: expectedKDFIterations,
NodeConfig: serverNodeConfig, NodeConfig: serverNodeConfig,
@ -167,6 +169,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, clientActiveAccount.KeyUID) clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, clientActiveAccount.KeyUID)
clientPayloadSourceConfig := PayloadSourceConfig{ clientPayloadSourceConfig := PayloadSourceConfig{
KeystorePath: clientKeystorePath, KeystorePath: clientKeystorePath,
DeviceType: "android",
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{ PayloadSourceSenderConfig: &PayloadSourceSenderConfig{
KeyUID: clientActiveAccount.KeyUID, KeyUID: clientActiveAccount.KeyUID,
Password: s.password, Password: s.password,
@ -176,7 +179,6 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.NoError(s.T(), clientBackend.Logout())
serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API) serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
bookmarks, err := serverBrowserAPI.GetBookmarks(context.TODO()) bookmarks, err := serverBrowserAPI.GetBookmarks(context.TODO())
@ -188,6 +190,35 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.Equal(s.T(), serverActiveAccount.Name, clientActiveAccount.Name) require.Equal(s.T(), serverActiveAccount.Name, clientActiveAccount.Name)
require.Equal(s.T(), serverActiveAccount.KDFIterations, expectedKDFIterations) require.Equal(s.T(), serverActiveAccount.KDFIterations, expectedKDFIterations)
serverMessenger := serverBackend.Messenger()
clientMessenger := clientBackend.Messenger()
require.True(s.T(), serverMessenger.HasPairedDevices())
require.True(s.T(), clientMessenger.HasPairedDevices())
err = clientMessenger.DisableInstallation(serverNodeConfig.ShhextConfig.InstallationID)
require.NoError(s.T(), err)
require.False(s.T(), clientMessenger.HasPairedDevices())
clientNodeConfig, err := clientBackend.GetNodeConfig()
require.NoError(s.T(), err)
err = serverMessenger.DisableInstallation(clientNodeConfig.ShhextConfig.InstallationID)
require.NoError(s.T(), err)
require.False(s.T(), serverMessenger.HasPairedDevices())
// repeat local pairing, we should expect no error after receiver logged in
cs, err = StartUpPairingServer(serverBackend, Receiving, string(serverConfigBytes))
require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err)
require.True(s.T(), clientMessenger.HasPairedDevices())
require.True(s.T(), serverMessenger.HasPairedDevices())
// test if it's okay when account already exist but not logged in
require.NoError(s.T(), serverBackend.Logout())
cs, err = StartUpPairingServer(serverBackend, Receiving, string(serverConfigBytes))
require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err)
} }
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() { func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
@ -198,6 +229,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
serverBackend := s.prepareBackendWithAccount(serverTmpDir) serverBackend := s.prepareBackendWithAccount(serverTmpDir)
defer func() { defer func() {
require.NoError(s.T(), clientBackend.Logout()) require.NoError(s.T(), clientBackend.Logout())
require.NoError(s.T(), serverBackend.Logout())
}() }()
serverActiveAccount, err := serverBackend.GetActiveAccount() serverActiveAccount, err := serverBackend.GetActiveAccount()
@ -205,6 +237,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, serverActiveAccount.KeyUID) serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, serverActiveAccount.KeyUID)
var config = PayloadSourceConfig{ var config = PayloadSourceConfig{
KeystorePath: serverKeystorePath, KeystorePath: serverKeystorePath,
DeviceType: "desktop",
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{ PayloadSourceSenderConfig: &PayloadSourceSenderConfig{
KeyUID: serverActiveAccount.KeyUID, KeyUID: serverActiveAccount.KeyUID,
Password: s.password, Password: s.password,
@ -233,6 +266,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
clientKeystoreDir := filepath.Join(clientTmpDir, keystoreDir) clientKeystoreDir := filepath.Join(clientTmpDir, keystoreDir)
clientPayloadSourceConfig := PayloadSourceConfig{ clientPayloadSourceConfig := PayloadSourceConfig{
KeystorePath: clientKeystoreDir, KeystorePath: clientKeystoreDir,
DeviceType: "iphone",
PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{ PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{
KDFIterations: expectedKDFIterations, KDFIterations: expectedKDFIterations,
NodeConfig: clientNodeConfig, NodeConfig: clientNodeConfig,
@ -245,8 +279,6 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.NoError(s.T(), serverBackend.Logout())
clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API) clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
bookmarks, err := clientBrowserAPI.GetBookmarks(context.TODO()) bookmarks, err := clientBrowserAPI.GetBookmarks(context.TODO())
require.NoError(s.T(), err) require.NoError(s.T(), err)
@ -257,6 +289,35 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.Equal(s.T(), serverActiveAccount.Name, clientActiveAccount.Name) require.Equal(s.T(), serverActiveAccount.Name, clientActiveAccount.Name)
require.Equal(s.T(), clientActiveAccount.KDFIterations, expectedKDFIterations) require.Equal(s.T(), clientActiveAccount.KDFIterations, expectedKDFIterations)
serverMessenger := serverBackend.Messenger()
clientMessenger := clientBackend.Messenger()
require.True(s.T(), serverMessenger.HasPairedDevices())
require.True(s.T(), clientMessenger.HasPairedDevices())
err = serverMessenger.DisableInstallation(clientNodeConfig.ShhextConfig.InstallationID)
require.NoError(s.T(), err)
require.False(s.T(), serverMessenger.HasPairedDevices())
serverNodeConfig, err := serverBackend.GetNodeConfig()
require.NoError(s.T(), err)
err = clientMessenger.DisableInstallation(serverNodeConfig.ShhextConfig.InstallationID)
require.NoError(s.T(), err)
require.False(s.T(), clientMessenger.HasPairedDevices())
// repeat local pairing, we should expect no error after receiver logged in
cs, err = StartUpPairingServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err)
require.True(s.T(), serverMessenger.HasPairedDevices())
require.True(s.T(), clientMessenger.HasPairedDevices())
// test if it's okay when account already exist but not logged in
require.NoError(s.T(), clientBackend.Logout())
cs, err = StartUpPairingServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err)
} }
func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) { func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) {

View File

@ -868,7 +868,7 @@ func (api *PublicAPI) VerifiedUntrustworthy(ctx context.Context, request *reques
} }
func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) { func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendPairInstallation(ctx) return api.service.messenger.SendPairInstallation(ctx, nil)
} }
func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error { func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error {