Sync all devices after initial pairing (#3047)

This commit is contained in:
frank 2023-01-06 20:21:14 +08:00 committed by GitHub
parent bea710c8be
commit ec7c0e9c7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2407 additions and 1079 deletions

View File

@ -1 +1 @@
0.117.3
0.118.0

View File

@ -31,6 +31,7 @@ import (
"github.com/status-im/status-go/node"
"github.com/status-im/status-go/nodecfg"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/ext"
"github.com/status-im/status-go/services/personal"
@ -1349,6 +1350,17 @@ func (b *GethStatusBackend) SignGroupMembership(content string) (string, error)
return crypto.SignStringAsHex(content, selectedChatAccount.AccountKey.PrivateKey)
}
func (b *GethStatusBackend) Messenger() *protocol.Messenger {
node := b.StatusNode()
if node != nil {
wakuExtService := node.WakuExtService()
if wakuExtService != nil {
return wakuExtService.Messenger()
}
}
return nil
}
// SignHash exposes vanilla ECDSA signing for signing a message for Swarm
func (b *GethStatusBackend) SignHash(hexEncodedHash string) (string, error) {
hash, err := hexutil.Decode(hexEncodedHash)

View File

@ -36,6 +36,7 @@ import (
"github.com/status-im/status-go/protocol/identity/colorhash"
"github.com/status-im/status-go/protocol/identity/emojihash"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/server/pairing"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/signal"
@ -938,42 +939,40 @@ func GenerateImages(filepath string, aX, aY, bX, bY int) string {
return string(data)
}
// GetConnectionStringForBeingBootstrapped starts a server.Receiving server.PairingServer
// then generates a server.ConnectionParams. Used when the device is Logged out or has no Account keys
// GetConnectionStringForBeingBootstrapped starts a pairing.Receiving pairing.PairingServer
// then generates a pairing.ConnectionParams. Used when the device is Logged out or has no Account keys
// and the device has no camera to read a QR code with
//
// Example: A desktop device (device without camera) receiving account data from mobile (device with camera)
func GetConnectionStringForBeingBootstrapped(configJSON string) string {
if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PairingPayloadSourceConfig is expected"))
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected"))
}
cs, err := server.StartUpPairingServer(statusBackend.GetMultiaccountDB(), server.Receiving, configJSON)
cs, err := pairing.StartUpPairingServer(statusBackend, pairing.Receiving, configJSON)
if err != nil {
return makeJSONResponse(err)
}
return cs
}
// GetConnectionStringForBootstrappingAnotherDevice starts a server.Sending server.PairingServer
// then generates a server.ConnectionParams. Used when the device is Logged in and therefore has Account keys
// GetConnectionStringForBootstrappingAnotherDevice starts a pairing.Sending pairing.Server
// then generates a pairing.ConnectionParams. Used when the device is Logged in and therefore has Account keys
// and the device might not have a camera
//
// Example: A mobile or desktop device (devices that MAY have a camera but MUST have a screen)
// sending account data to a mobile (device with camera)
func GetConnectionStringForBootstrappingAnotherDevice(configJSON string) string {
if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PairingPayloadSourceConfig is expected"))
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected"))
}
cs, err := server.StartUpPairingServer(statusBackend.GetMultiaccountDB(), server.Sending, configJSON)
cs, err := pairing.StartUpPairingServer(statusBackend, pairing.Sending, configJSON)
if err != nil {
return makeJSONResponse(err)
}
return cs
}
// InputConnectionStringForBootstrapping starts a server.PairingClient
// InputConnectionStringForBootstrapping starts a pairing.Client
// The given server.ConnectionParams string will determine the server.Mode
//
// server.Mode = server.Sending
@ -988,10 +987,10 @@ func GetConnectionStringForBootstrappingAnotherDevice(configJSON string) string
// a device with a screen (mobile or desktop devices)
func InputConnectionStringForBootstrapping(cs, configJSON string) string {
if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PairingPayloadSourceConfig is expected"))
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected"))
}
err := server.StartUpPairingClient(statusBackend.GetMultiaccountDB(), cs, configJSON)
err := pairing.StartUpPairingClient(statusBackend, cs, configJSON)
return makeJSONResponse(err)
}

View File

@ -120,15 +120,29 @@ func NewDB(db *sql.DB) (*Database, error) {
}
// DB Gets db sql.DB
func (db Database) DB() *sql.DB {
func (db *Database) DB() *sql.DB {
return db.db
}
// Close closes database.
func (db Database) Close() error {
func (db *Database) Close() error {
return db.db.Close()
}
func (db *Database) GetAccountsByKeyUID(keyUID string) ([]*Account, error) {
accounts, err := db.GetAccounts()
if err != nil {
return nil, err
}
filteredAccounts := make([]*Account, 0)
for _, account := range accounts {
if account.KeyUID == keyUID {
filteredAccounts = append(filteredAccounts, account)
}
}
return filteredAccounts, nil
}
func (db *Database) GetAccounts() ([]*Account, error) {
rows, err := db.db.Query(`SELECT address, wallet, chat, type, storage, pubkey, path, name, emoji, color, hidden, derived_from, clock, key_uid
FROM accounts ORDER BY created_at`)

View File

@ -211,6 +211,10 @@ func (b *StatusNode) AccountService() *accountssvc.Service {
return b.accountsSrvc
}
func (b *StatusNode) BrowserService() *browsers.Service {
return b.browsersSrvc
}
func (b *StatusNode) WakuService() *waku.Waku {
return b.wakuSrvc
}

View File

@ -157,6 +157,9 @@ type Messenger struct {
mailPeersMutex sync.Mutex
handleMessagesMutex sync.Mutex
handleImportMessagesMutex sync.Mutex
// flag to disable checking #hasPairedDevices
localPairing bool
}
type connStatus int
@ -1335,7 +1338,7 @@ func (m *Messenger) watchIdentityImageChanges() {
for {
select {
case <-channel:
err := m.syncProfilePictures()
err := m.syncProfilePictures(m.dispatchMessage)
if err != nil {
m.logger.Error("failed to sync profile pictures to paired devices", zap.Error(err))
}
@ -1718,9 +1721,16 @@ func (m *Messenger) ReSendChatMessage(ctx context.Context, messageID string) err
return m.reSendRawMessage(ctx, messageID)
}
func (m *Messenger) SetLocalPairing(localPairing bool) {
m.localPairing = localPairing
}
func (m *Messenger) hasPairedDevices() bool {
logger := m.logger.Named("hasPairedDevices")
if m.localPairing {
return true
}
var count int
m.allInstallations.Range(func(installationID string, installation *multidevice.Installation) (shouldContinue bool) {
if installation.Enabled {
@ -2166,7 +2176,7 @@ func (m *Messenger) ShareImageMessage(request *requests.ShareImageMessage) (*Mes
return response, nil
}
func (m *Messenger) syncProfilePictures() error {
func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -2207,12 +2217,14 @@ func (m *Messenger) syncProfilePictures() error {
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_PROFILE_PICTURE,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2223,7 +2235,11 @@ func (m *Messenger) syncProfilePictures() error {
// SyncDevices sends all public chats and contacts to paired devices
// TODO remove use of photoPath in contacts
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) (err error) {
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string, rawMessageHandler RawMessageHandler) (err error) {
if rawMessageHandler == nil {
rawMessageHandler = m.dispatchMessage
}
myID := contactIDFromPublicKey(&m.identity.PublicKey)
displayName, err := m.settings.DisplayName()
@ -2231,14 +2247,14 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
return err
}
if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, photoPath); err != nil {
if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, photoPath, rawMessageHandler); err != nil {
return err
}
m.allChats.Range(func(chatID string, chat *Chat) (shouldContinue bool) {
isPublicChat := !chat.Timeline() && !chat.ProfileUpdates() && chat.Public()
if isPublicChat && chat.Active {
err = m.syncPublicChat(ctx, chat)
err = m.syncPublicChat(ctx, chat, rawMessageHandler)
if err != nil {
return false
}
@ -2251,7 +2267,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
}
if !pending {
err = m.syncChatRemoving(ctx, chatID)
err = m.syncChatRemoving(ctx, chatID, rawMessageHandler)
if err != nil {
return false
}
@ -2259,14 +2275,14 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
}
if (isPublicChat || chat.OneToOne() || chat.PrivateGroupChat() || chat.CommunityChat()) && chat.Active {
err := m.syncChatMessagesRead(ctx, chatID, chat.ReadMessagesAtClockValue)
err := m.syncChatMessagesRead(ctx, chatID, chat.ReadMessagesAtClockValue, rawMessageHandler)
if err != nil {
return false
}
}
if isPublicChat && chat.Active && chat.DeletedAtClockValue > 0 {
err = m.syncClearHistory(ctx, chat)
err = m.syncClearHistory(ctx, chat, rawMessageHandler)
if err != nil {
return false
}
@ -2281,7 +2297,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) {
if contact.ID != myID &&
(contact.LocalNickname != "" || contact.Added || contact.Blocked) {
if err = m.syncContact(ctx, contact); err != nil {
if err = m.syncContact(ctx, contact, rawMessageHandler); err != nil {
return false
}
}
@ -2293,7 +2309,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
return err
}
for _, c := range cs {
if err = m.syncCommunity(ctx, c); err != nil {
if err = m.syncCommunity(ctx, c, rawMessageHandler); err != nil {
return err
}
}
@ -2303,7 +2319,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
return err
}
for _, b := range bookmarks {
if err = m.SyncBookmark(ctx, b); err != nil {
if err = m.SyncBookmark(ctx, b, rawMessageHandler); err != nil {
return err
}
}
@ -2313,7 +2329,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
return err
}
for id, ts := range trustedUsers {
if err = m.SyncTrustedUser(ctx, id, ts); err != nil {
if err = m.SyncTrustedUser(ctx, id, ts, rawMessageHandler); err != nil {
return err
}
}
@ -2323,17 +2339,17 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
return err
}
for i := range verificationRequests {
if err = m.SyncVerificationRequest(ctx, &verificationRequests[i]); err != nil {
if err = m.SyncVerificationRequest(ctx, &verificationRequests[i], rawMessageHandler); err != nil {
return err
}
}
err = m.syncSettings()
err = m.syncSettings(rawMessageHandler)
if err != nil {
return err
}
err = m.syncProfilePictures()
err = m.syncProfilePictures(rawMessageHandler)
if err != nil {
return err
}
@ -2352,14 +2368,14 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
for id, state := range ids {
if state == common.ContactRequestStateAccepted || state == common.ContactRequestStateDismissed {
accepted := state == common.ContactRequestStateAccepted
err := m.syncContactRequestDecision(ctx, id, accepted)
err := m.syncContactRequestDecision(ctx, id, accepted, rawMessageHandler)
if err != nil {
return err
}
}
}
err = m.syncWallets(accounts)
err = m.syncWallets(accounts, rawMessageHandler)
if err != nil {
return err
}
@ -2372,7 +2388,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string)
for i := range savedAddresses {
sa := savedAddresses[i]
err = m.syncSavedAddress(ctx, sa)
err = m.syncSavedAddress(ctx, sa, rawMessageHandler)
if err != nil {
return err
}
@ -2385,7 +2401,7 @@ func (m *Messenger) SaveAccounts(accs []*accounts.Account) error {
if err != nil {
return err
}
return m.syncWallets(accs)
return m.syncWallets(accs, m.dispatchMessage)
}
func (m *Messenger) DeleteAccount(address types.Address) error {
@ -2402,11 +2418,11 @@ func (m *Messenger) DeleteAccount(address types.Address) error {
acc.Removed = true
accs := []*accounts.Account{acc}
return m.syncWallets(accs)
return m.syncWallets(accs, m.dispatchMessage)
}
// syncWallets syncs all wallets with paired devices
func (m *Messenger) syncWallets(accs []*accounts.Account) error {
func (m *Messenger) syncWallets(accs []*accounts.Account, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -2454,12 +2470,14 @@ func (m *Messenger) syncWallets(accs []*accounts.Account) error {
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_WALLET_ACCOUNT,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2468,7 +2486,7 @@ func (m *Messenger) syncWallets(accs []*accounts.Account) error {
return m.saveChat(chat)
}
func (m *Messenger) syncContactRequestDecision(ctx context.Context, requestID string, accepted bool) error {
func (m *Messenger) syncContactRequestDecision(ctx context.Context, requestID string, accepted bool, rawMessageHandler RawMessageHandler) error {
m.logger.Info("syncContactRequestDecision", zap.Any("from", requestID))
if !m.hasPairedDevices() {
return nil
@ -2494,12 +2512,14 @@ func (m *Messenger) syncContactRequestDecision(ctx context.Context, requestID st
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_CONTACT_REQUEST_DECISION,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2570,7 +2590,7 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
}
// syncPublicChat sync a public chat with paired devices
func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error {
func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat, rawMessageHandler RawMessageHandler) error {
var err error
if !m.hasPairedDevices() {
return nil
@ -2586,12 +2606,14 @@ func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_PUBLIC_CHAT,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2600,7 +2622,7 @@ func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error
return m.saveChat(chat)
}
func (m *Messenger) syncClearHistory(ctx context.Context, publicChat *Chat) error {
func (m *Messenger) syncClearHistory(ctx context.Context, publicChat *Chat, rawMessageHandler RawMessageHandler) error {
var err error
if !m.hasPairedDevices() {
return nil
@ -2617,12 +2639,14 @@ func (m *Messenger) syncClearHistory(ctx context.Context, publicChat *Chat) erro
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_CLEAR_HISTORY,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2631,7 +2655,7 @@ func (m *Messenger) syncClearHistory(ctx context.Context, publicChat *Chat) erro
return m.saveChat(chat)
}
func (m *Messenger) syncChatRemoving(ctx context.Context, id string) error {
func (m *Messenger) syncChatRemoving(ctx context.Context, id string, rawMessageHandler RawMessageHandler) error {
var err error
if !m.hasPairedDevices() {
return nil
@ -2647,12 +2671,14 @@ func (m *Messenger) syncChatRemoving(ctx context.Context, id string) error {
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_CHAT_REMOVED,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2662,7 +2688,7 @@ func (m *Messenger) syncChatRemoving(ctx context.Context, id string) error {
}
// syncContact sync as contact with paired devices
func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
func (m *Messenger) syncContact(ctx context.Context, contact *Contact, rawMessageHandler RawMessageHandler) error {
var err error
if contact.IsSyncing {
return nil
@ -2702,12 +2728,14 @@ func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_CONTACT,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2716,7 +2744,7 @@ func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
return m.saveChat(chat)
}
func (m *Messenger) syncCommunity(ctx context.Context, community *communities.Community) error {
func (m *Messenger) syncCommunity(ctx context.Context, community *communities.Community, rawMessageHandler RawMessageHandler) error {
logger := m.logger.Named("syncCommunity")
if !m.hasPairedDevices() {
logger.Debug("device has no paired devices")
@ -2747,12 +2775,14 @@ func (m *Messenger) syncCommunity(ctx context.Context, community *communities.Co
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_COMMUNITY,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -2762,7 +2792,7 @@ func (m *Messenger) syncCommunity(ctx context.Context, community *communities.Co
return m.saveChat(chat)
}
func (m *Messenger) SyncBookmark(ctx context.Context, bookmark *browsers.Bookmark) error {
func (m *Messenger) SyncBookmark(ctx context.Context, bookmark *browsers.Bookmark, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -2782,20 +2812,22 @@ func (m *Messenger) SyncBookmark(ctx context.Context, bookmark *browsers.Bookmar
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_BOOKMARK,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
func (m *Messenger) SyncTrustedUser(ctx context.Context, publicKey string, ts verification.TrustStatus) error {
func (m *Messenger) SyncTrustedUser(ctx context.Context, publicKey string, ts verification.TrustStatus, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -2812,20 +2844,23 @@ func (m *Messenger) SyncTrustedUser(ctx context.Context, publicKey string, ts ve
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_TRUSTED_USER,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
func (m *Messenger) SyncVerificationRequest(ctx context.Context, vr *verification.Request) error {
func (m *Messenger) SyncVerificationRequest(ctx context.Context, vr *verification.Request, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -2848,15 +2883,18 @@ func (m *Messenger) SyncVerificationRequest(ctx context.Context, vr *verificatio
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_VERIFICATION_REQUEST,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
@ -3611,6 +3649,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Debug("Handling SyncChatRemoved", zap.Any("message", p))
err := m.HandleSyncChatRemoved(messageState, p)
if err != nil {
allMessagesProcessed = false
logger.Warn("failed to handle sync removing chat", zap.Error(err))
continue
}
@ -3626,7 +3665,8 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Debug("Handling SyncChatMessagesRead", zap.Any("message", p))
err := m.HandleSyncChatMessagesRead(messageState, p)
if err != nil {
logger.Warn("failed to handle sync removing chat", zap.Error(err))
allMessagesProcessed = false
logger.Warn("failed to handle sync chat message read", zap.Error(err))
continue
}
@ -4094,7 +4134,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
p := msg.ParsedMessage.Interface().(protobuf.SyncContactRequestDecision)
err := m.HandleSyncContactRequestDecision(messageState, p)
if err != nil {
logger.Warn("failed to handle SyncContactRequestDecisio", zap.Error(err))
logger.Warn("failed to handle SyncContactRequestDecision", zap.Error(err))
continue
}
case protobuf.SyncSavedAddress:
@ -4186,6 +4226,11 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
}
}
return m.saveDataAndPrepareResponse(messageState)
}
func (m *Messenger) saveDataAndPrepareResponse(messageState *ReceivedMessageState) (*MessengerResponse, error) {
var err error
var contactsToSave []*Contact
messageState.ModifiedContacts.Range(func(id string, value bool) (shouldContinue bool) {
contact, ok := messageState.AllContacts.Load(id)
@ -4519,7 +4564,7 @@ func (m *Messenger) MarkMessagesSeen(chatID string, ids []string) (uint64, uint6
return count, countWithMentions, nil
}
func (m *Messenger) syncChatMessagesRead(ctx context.Context, chatID string, clock uint64) error {
func (m *Messenger) syncChatMessagesRead(ctx context.Context, chatID string, clock uint64, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -4535,12 +4580,14 @@ func (m *Messenger) syncChatMessagesRead(ctx context.Context, chatID string, clo
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_CHAT_MESSAGES_READ,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
return err
}
@ -4557,7 +4604,7 @@ func (m *Messenger) markAllRead(chatID string, clock uint64, shouldBeSynced bool
}
if shouldBeSynced {
err := m.syncChatMessagesRead(context.Background(), chatID, clock)
err := m.syncChatMessagesRead(context.Background(), chatID, clock, m.dispatchMessage)
if err != nil {
return err
}
@ -4676,7 +4723,7 @@ func (m *Messenger) muteChat(chat *Chat, contact *Contact) error {
m.allChats.Store(chat.ID, chat)
if contact != nil {
err := m.syncContact(context.Background(), contact)
err := m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return err
}
@ -4712,7 +4759,7 @@ func (m *Messenger) unmuteChat(chat *Chat, contact *Contact) error {
m.allChats.Store(chat.ID, chat)
if contact != nil {
err := m.syncContact(context.Background(), contact)
err := m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return err
}

View File

@ -14,7 +14,7 @@ func (m *Messenger) AddBookmark(ctx context.Context, bookmark browsers.Bookmark)
if err != nil {
return err
}
return m.SyncBookmark(ctx, &bmr)
return m.SyncBookmark(ctx, &bmr, m.dispatchMessage)
}
func (m *Messenger) RemoveBookmark(ctx context.Context, url string) error {
@ -28,7 +28,7 @@ func (m *Messenger) RemoveBookmark(ctx context.Context, url string) error {
if err != nil {
return err
}
return m.SyncBookmark(ctx, bmr)
return m.SyncBookmark(ctx, bmr, m.dispatchMessage)
}
func (m *Messenger) UpdateBookmark(ctx context.Context, oldURL string, bookmark browsers.Bookmark) error {
@ -36,7 +36,7 @@ func (m *Messenger) UpdateBookmark(ctx context.Context, oldURL string, bookmark
if err != nil {
return err
}
return m.SyncBookmark(ctx, &bookmark)
return m.SyncBookmark(ctx, &bookmark, m.dispatchMessage)
}
func (m *Messenger) GarbageCollectRemovedBookmarks() error {

View File

@ -164,7 +164,7 @@ func (m *Messenger) createPublicChat(chatID string, response *MessengerResponse)
// Sync if it was created
if !ok || !wasActive {
if err := m.syncPublicChat(context.Background(), chat); err != nil {
if err := m.syncPublicChat(context.Background(), chat, m.dispatchMessage); err != nil {
return nil, err
}
}
@ -407,7 +407,7 @@ func (m *Messenger) deactivateChat(chatID string, deactivationClock uint64, shou
// TODO: Remove filters
if shouldBeSynced {
err := m.syncChatRemoving(context.Background(), chat.ID)
err := m.syncChatRemoving(context.Background(), chat.ID, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -443,7 +443,7 @@ func (m *Messenger) saveChat(chat *Chat) error {
// Sync chat if it's a new active public chat, but not a timeline chat
if !ok && chat.Active && chat.Public() && !chat.ProfileUpdates() && !chat.Timeline() {
if err := m.syncPublicChat(context.Background(), chat); err != nil {
if err := m.syncPublicChat(context.Background(), chat, m.dispatchMessage); err != nil {
return err
}
}
@ -565,7 +565,7 @@ func (m *Messenger) clearHistory(id string) (*MessengerResponse, error) {
}
}
err = m.syncClearHistory(context.Background(), chat)
err = m.syncClearHistory(context.Background(), chat, m.dispatchMessage)
if err != nil {
return nil, err
}

View File

@ -368,7 +368,7 @@ func (m *Messenger) JoinCommunity(ctx context.Context, communityID types.HexByte
}
if com, ok := mr.communities[communityID.String()]; ok {
err = m.syncCommunity(context.Background(), com)
err = m.syncCommunity(context.Background(), com, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -493,7 +493,7 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun
if err != nil {
return nil, err
}
err = m.syncCommunity(context.Background(), community)
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -822,7 +822,7 @@ func (m *Messenger) LeaveCommunity(communityID types.HexBytes) (*MessengerRespon
m.communitiesManager.StopHistoryArchiveTasksInterval(communityID)
if com, ok := mr.communities[communityID.String()]; ok {
err = m.syncCommunity(context.Background(), com)
err = m.syncCommunity(context.Background(), com, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -1052,7 +1052,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity, createDef
response.AddCommunity(community)
response.AddCommunitySettings(&communitySettings)
err = m.syncCommunity(context.Background(), community)
err = m.syncCommunity(context.Background(), community, m.dispatchMessage)
if err != nil {
return nil, err
}

View File

@ -85,7 +85,7 @@ func (m *Messenger) SendContactVerificationRequest(ctx context.Context, contactI
}
// We sync the contact with the other devices
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -100,7 +100,7 @@ func (m *Messenger) SendContactVerificationRequest(ctx context.Context, contactI
return nil, err
}
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -184,7 +184,7 @@ func (m *Messenger) CancelVerificationRequest(ctx context.Context, id string) (*
}
// We sync the contact with the other devices
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -210,7 +210,7 @@ func (m *Messenger) CancelVerificationRequest(ctx context.Context, id string) (*
response.AddVerificationRequest(verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -302,7 +302,7 @@ func (m *Messenger) AcceptContactVerificationRequest(ctx context.Context, id str
return nil, err
}
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -396,7 +396,7 @@ func (m *Messenger) VerifiedTrusted(ctx context.Context, request *requests.Verif
return nil, err
}
err = m.SyncTrustedUser(context.Background(), contactID, verification.TrustStatusTRUSTED)
err = m.SyncTrustedUser(context.Background(), contactID, verification.TrustStatusTRUSTED, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -436,13 +436,13 @@ func (m *Messenger) VerifiedTrusted(ctx context.Context, request *requests.Verif
return nil, err
}
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
// We sync the contact with the other devices
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -501,7 +501,7 @@ func (m *Messenger) VerifiedUntrustworthy(ctx context.Context, request *requests
return nil, err
}
err = m.SyncTrustedUser(context.Background(), contactID, verification.TrustStatusUNTRUSTWORTHY)
err = m.SyncTrustedUser(context.Background(), contactID, verification.TrustStatusUNTRUSTWORTHY, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -541,13 +541,13 @@ func (m *Messenger) VerifiedUntrustworthy(ctx context.Context, request *requests
return nil, err
}
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
// We sync the contact with the other devices
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -624,7 +624,7 @@ func (m *Messenger) DeclineContactVerificationRequest(ctx context.Context, id st
response.AddVerificationRequest(verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest)
err = m.SyncVerificationRequest(context.Background(), verifRequest, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -687,7 +687,7 @@ func (m *Messenger) MarkAsTrusted(ctx context.Context, contactID string) error {
return err
}
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusTRUSTED)
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusTRUSTED, m.dispatchMessage)
}
func (m *Messenger) MarkAsUntrustworthy(ctx context.Context, contactID string) error {
@ -696,7 +696,7 @@ func (m *Messenger) MarkAsUntrustworthy(ctx context.Context, contactID string) e
return err
}
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusUNTRUSTWORTHY)
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusUNTRUSTWORTHY, m.dispatchMessage)
}
func (m *Messenger) RemoveTrustStatus(ctx context.Context, contactID string) error {
@ -705,7 +705,7 @@ func (m *Messenger) RemoveTrustStatus(ctx context.Context, contactID string) err
return err
}
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusUNKNOWN)
return m.SyncTrustedUser(ctx, contactID, verification.TrustStatusUNKNOWN, m.dispatchMessage)
}
func (m *Messenger) GetTrustStatus(contactID string) (verification.TrustStatus, error) {
@ -775,7 +775,7 @@ func (m *Messenger) HandleRequestContactVerification(state *ReceivedMessageState
}
m.logger.Info("SAVED", zap.String("id", persistedVR.ID))
err = m.SyncVerificationRequest(context.Background(), persistedVR)
err = m.SyncVerificationRequest(context.Background(), persistedVR, m.dispatchMessage)
if err != nil {
return err
}
@ -865,7 +865,7 @@ func (m *Messenger) HandleAcceptContactVerification(state *ReceivedMessageState,
return err
}
err = m.SyncVerificationRequest(context.Background(), persistedVR)
err = m.SyncVerificationRequest(context.Background(), persistedVR, m.dispatchMessage)
if err != nil {
return err
}
@ -952,7 +952,7 @@ func (m *Messenger) HandleDeclineContactVerification(state *ReceivedMessageState
return err
}
err = m.SyncVerificationRequest(context.Background(), persistedVR)
err = m.SyncVerificationRequest(context.Background(), persistedVR, m.dispatchMessage)
if err != nil {
return err
}
@ -1005,7 +1005,7 @@ func (m *Messenger) HandleCancelContactVerification(state *ReceivedMessageState,
return err
}
err = m.SyncVerificationRequest(context.Background(), persistedVR)
err = m.SyncVerificationRequest(context.Background(), persistedVR, m.dispatchMessage)
if err != nil {
return err
}

View File

@ -37,7 +37,7 @@ func (m *Messenger) AcceptContactRequest(ctx context.Context, request *requests.
return nil, err
}
err = m.syncContactRequestDecision(ctx, request.ID.String(), true)
err = m.syncContactRequestDecision(ctx, request.ID.String(), true, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -195,7 +195,7 @@ func (m *Messenger) DismissContactRequest(ctx context.Context, request *requests
return nil, err
}
err = m.syncContactRequestDecision(ctx, request.ID.String(), false)
err = m.syncContactRequestDecision(ctx, request.ID.String(), false, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -315,7 +315,7 @@ func (m *Messenger) addContact(pubKey, ensName, nickname, displayName, contactRe
if !syncing {
// We sync the contact with the other devices
err := m.syncContact(context.Background(), contact)
err := m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -377,7 +377,7 @@ func (m *Messenger) addContact(pubKey, ensName, nickname, displayName, contactRe
}
// Finally we send a contact update so they are notified we added them
response, err := m.sendContactUpdate(context.Background(), pubKey, displayName, ensName, "")
response, err := m.sendContactUpdate(context.Background(), pubKey, displayName, ensName, "", m.dispatchMessage)
if err != nil {
return nil, err
}
@ -452,7 +452,7 @@ func (m *Messenger) removeContact(ctx context.Context, response *MessengerRespon
return err
}
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return err
}
@ -580,7 +580,7 @@ func (m *Messenger) SetContactLocalNickname(request *requests.SetContactLocalNic
response := &MessengerResponse{}
response.Contacts = []*Contact{contact}
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -621,7 +621,7 @@ func (m *Messenger) blockContact(contactID string, isDesktopFunc bool) ([]*Chat,
m.allChats.Delete(buildProfileChatID(contact.ID))
}
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return nil, err
}
@ -696,7 +696,7 @@ func (m *Messenger) UnblockContact(contactID string) error {
m.allContacts.Store(contact.ID, contact)
err = m.syncContact(context.Background(), contact)
err = m.syncContact(context.Background(), contact, m.dispatchMessage)
if err != nil {
return err
}
@ -719,14 +719,14 @@ func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImag
return err
}
if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, profileImage); err != nil {
if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, profileImage, m.dispatchMessage); err != nil {
return err
}
// TODO: This should not be sending paired messages, as we do it above
m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) {
if contact.Added {
if _, err = m.sendContactUpdate(ctx, contact.ID, displayName, ensName, profileImage); err != nil {
if _, err = m.sendContactUpdate(ctx, contact.ID, displayName, ensName, profileImage, m.dispatchMessage); err != nil {
return false
}
}
@ -748,10 +748,10 @@ func (m *Messenger) SendContactUpdate(ctx context.Context, chatID, ensName, prof
return nil, err
}
return m.sendContactUpdate(ctx, chatID, displayName, ensName, profileImage)
return m.sendContactUpdate(ctx, chatID, displayName, ensName, profileImage, m.dispatchMessage)
}
func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, displayName, ensName, profileImage string) (*MessengerResponse, error) {
func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, displayName, ensName, profileImage string, rawMessageHandler RawMessageHandler) (*MessengerResponse, error) {
var response MessengerResponse
contact, ok := m.allContacts.Load(chatID)
@ -785,12 +785,14 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, displayName,
return nil, err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return nil, err
}

View File

@ -725,7 +725,7 @@ func (m *Messenger) leaveGroupChat(ctx context.Context, response *MessengerRespo
}
if remove && shouldBeSynced {
err := m.syncChatRemoving(ctx, chat.ID)
err := m.syncChatRemoving(ctx, chat.ID, m.dispatchMessage)
if err != nil {
return nil, err
}

View File

@ -210,7 +210,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() {
s.Require().NoError(err)
// sync
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image")
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil)
s.Require().NoError(err)
var allChats []*Chat

View File

@ -17,7 +17,7 @@ func (m *Messenger) UpsertSavedAddress(ctx context.Context, sa wallet.SavedAddre
if err != nil {
return err
}
return m.syncNewSavedAddress(ctx, &sa, updatedClock)
return m.syncNewSavedAddress(ctx, &sa, updatedClock, m.dispatchMessage)
}
func (m *Messenger) DeleteSavedAddress(ctx context.Context, chainID uint64, address gethcommon.Address) error {
@ -25,14 +25,14 @@ func (m *Messenger) DeleteSavedAddress(ctx context.Context, chainID uint64, addr
if err != nil {
return err
}
return m.syncDeletedSavedAddress(ctx, chainID, address, updatedClock)
return m.syncDeletedSavedAddress(ctx, chainID, address, updatedClock, m.dispatchMessage)
}
func (m *Messenger) garbageCollectRemovedSavedAddresses() error {
return m.savedAddressesManager.DeleteSoftRemovedSavedAddresses(uint64(time.Now().AddDate(0, 0, -30).Unix()))
}
func (m *Messenger) dispatchSyncSavedAddress(ctx context.Context, syncMessage protobuf.SyncSavedAddress) error {
func (m *Messenger) dispatchSyncSavedAddress(ctx context.Context, syncMessage protobuf.SyncSavedAddress, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
@ -44,12 +44,14 @@ func (m *Messenger) dispatchSyncSavedAddress(ctx context.Context, syncMessage pr
return err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_SAVED_ADDRESS,
ResendAutomatically: true,
})
}
_, err = rawMessageHandler(ctx, rawMessage)
if err != nil {
return err
}
@ -58,32 +60,32 @@ func (m *Messenger) dispatchSyncSavedAddress(ctx context.Context, syncMessage pr
return m.saveChat(chat)
}
func (m *Messenger) syncNewSavedAddress(ctx context.Context, savedAddress *wallet.SavedAddress, updateClock uint64) error {
func (m *Messenger) syncNewSavedAddress(ctx context.Context, savedAddress *wallet.SavedAddress, updateClock uint64, rawMessageHandler RawMessageHandler) error {
return m.dispatchSyncSavedAddress(ctx, protobuf.SyncSavedAddress{
Address: savedAddress.Address.Bytes(),
Name: savedAddress.Name,
Favourite: savedAddress.Favourite,
ChainId: savedAddress.ChainID,
UpdateClock: updateClock,
})
}, rawMessageHandler)
}
func (m *Messenger) syncDeletedSavedAddress(ctx context.Context, chainID uint64, address gethcommon.Address, updateClock uint64) error {
func (m *Messenger) syncDeletedSavedAddress(ctx context.Context, chainID uint64, address gethcommon.Address, updateClock uint64, rawMessageHandler RawMessageHandler) error {
return m.dispatchSyncSavedAddress(ctx, protobuf.SyncSavedAddress{
Address: address.Bytes(),
ChainId: chainID,
UpdateClock: updateClock,
Removed: true,
})
}, rawMessageHandler)
}
func (m *Messenger) syncSavedAddress(ctx context.Context, savedAddress wallet.SavedAddress) (err error) {
func (m *Messenger) syncSavedAddress(ctx context.Context, savedAddress wallet.SavedAddress, rawMessageHandler RawMessageHandler) (err error) {
if savedAddress.Removed {
if err = m.syncDeletedSavedAddress(ctx, savedAddress.ChainID, savedAddress.Address, savedAddress.UpdateClock); err != nil {
if err = m.syncDeletedSavedAddress(ctx, savedAddress.ChainID, savedAddress.Address, savedAddress.UpdateClock, rawMessageHandler); err != nil {
return err
}
} else {
if err = m.syncNewSavedAddress(ctx, &savedAddress, savedAddress.UpdateClock); err != nil {
if err = m.syncNewSavedAddress(ctx, &savedAddress, savedAddress.UpdateClock, rawMessageHandler); err != nil {
return err
}
}

View File

@ -109,7 +109,7 @@ func (s *MessengerSyncBookmarkSuite) TestSyncBookmark() {
s.Require().NoError(err)
// sync
err = s.m.SyncBookmark(context.Background(), &bookmark)
err = s.m.SyncBookmark(context.Background(), &bookmark, s.m.dispatchMessage)
s.Require().NoError(err)
// Wait for the message to reach its destination
@ -133,7 +133,7 @@ func (s *MessengerSyncBookmarkSuite) TestSyncBookmark() {
// sync removed state
bookmark.Removed = true
err = s.m.SyncBookmark(context.Background(), &bookmark)
err = s.m.SyncBookmark(context.Background(), &bookmark, s.m.dispatchMessage)
s.Require().NoError(err)
// Wait for the message to reach its destination

View File

@ -0,0 +1,210 @@
package protocol
import (
"context"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/protocol/common"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"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)
func (m *Messenger) HandleSyncRawMessages(rawMessages []*protobuf.RawMessage) error {
state := m.buildMessageState()
for _, rawMessage := range rawMessages {
switch rawMessage.GetMessageType() {
case protobuf.ApplicationMetadataMessage_CONTACT_UPDATE:
var message protobuf.ContactUpdate
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleContactUpdate(state, message)
if err != nil {
m.logger.Warn("failed to HandleContactUpdate when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_PUBLIC_CHAT:
var message protobuf.SyncInstallationPublicChat
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
addedChat := m.HandleSyncInstallationPublicChat(state, message)
if addedChat != nil {
_, err = m.createPublicChat(addedChat.ID, state.Response)
if err != nil {
m.logger.Error("error createPublicChat when HandleSyncRawMessages", zap.Error(err))
continue
}
}
case protobuf.ApplicationMetadataMessage_SYNC_CHAT_REMOVED:
var message protobuf.SyncChatRemoved
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncChatRemoved(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncChatRemoved when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_CHAT_MESSAGES_READ:
var message protobuf.SyncChatMessagesRead
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncChatMessagesRead(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncChatMessagesRead when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_CLEAR_HISTORY:
var message protobuf.SyncClearHistory
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncClearHistory(state, message)
if err != nil {
m.logger.Error("failed to handleSyncClearHistory when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_CONTACT:
var message protobuf.SyncInstallationContactV2
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncInstallationContact(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncInstallationContact when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_COMMUNITY:
var message protobuf.SyncCommunity
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncCommunity(state, message)
if err != nil {
m.logger.Error("failed to handleSyncCommunity when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_BOOKMARK:
var message protobuf.SyncBookmark
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncBookmark(state, message)
if err != nil {
m.logger.Error("failed to handleSyncBookmark when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_TRUSTED_USER:
var message protobuf.SyncTrustedUser
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncTrustedUser(state, message)
if err != nil {
m.logger.Error("failed to handleSyncTrustedUser when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_VERIFICATION_REQUEST:
var message protobuf.SyncVerificationRequest
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncVerificationRequest(state, message)
if err != nil {
m.logger.Error("failed to handleSyncVerificationRequest when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_SETTING:
var message protobuf.SyncSetting
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncSetting(state, &message)
if err != nil {
m.logger.Error("failed to handleSyncSetting when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_PROFILE_PICTURE:
var message protobuf.SyncProfilePictures
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncProfilePictures(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncProfilePictures when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_CONTACT_REQUEST_DECISION:
var message protobuf.SyncContactRequestDecision
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncContactRequestDecision(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncContactRequestDecision when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_WALLET_ACCOUNT:
var message protobuf.SyncWalletAccounts
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.HandleSyncWalletAccount(state, message)
if err != nil {
m.logger.Error("failed to HandleSyncWalletAccount when HandleSyncRawMessages", zap.Error(err))
continue
}
case protobuf.ApplicationMetadataMessage_SYNC_SAVED_ADDRESS:
var message protobuf.SyncSavedAddress
err := proto.Unmarshal(rawMessage.GetPayload(), &message)
if err != nil {
return err
}
err = m.handleSyncSavedAddress(state, message)
if err != nil {
m.logger.Error("failed to handleSyncSavedAddress when HandleSyncRawMessages", zap.Error(err))
continue
}
}
}
response, err := m.saveDataAndPrepareResponse(state)
if err != nil {
return err
}
publishMessengerResponse(response)
return nil
}
// this is a copy implementation of the one in ext/service.go, we should refactor this?
func publishMessengerResponse(response *MessengerResponse) {
if !response.IsEmpty() {
notifications := response.Notifications()
// Clear notifications as not used for now
response.ClearNotifications()
signal.SendNewMessages(response)
localnotifications.PushMessages(notifications)
}
}

View File

@ -142,7 +142,7 @@ func (s *MessengerSyncSavedAddressesSuite) TestSyncExistingSavedAddresses() {
s.Require().NoError(err)
// Trigger's a sync between devices
err = s.main.SyncDevices(context.Background(), "ens-name", "profile-image")
err = s.main.SyncDevices(context.Background(), "ens-name", "profile-image", nil)
s.Require().NoError(err)
// Wait and check that saved addresses are synced

View File

@ -52,7 +52,7 @@ func (m *Messenger) prepareSyncSettingsMessages(currentClock uint64) (resultRaw
return
}
func (m *Messenger) syncSettings() error {
func (m *Messenger) syncSettings(rawMessageHandler RawMessageHandler) error {
logger := m.logger.Named("syncSettings")
clock, _ := m.getLastClockWithRelatedChat()
@ -64,7 +64,7 @@ func (m *Messenger) syncSettings() error {
}
for _, rm := range rawMessages {
_, err := m.dispatchMessage(context.Background(), *rm)
_, err := rawMessageHandler(context.Background(), *rm)
if err != nil {
logger.Error("dispatchMessage", zap.Error(err))
return err

View File

@ -96,7 +96,7 @@ func (s *MessengerSyncVerificationRequests) TestSyncVerificationRequests() {
s.Require().NoError(err)
// sync
err = s.m.SyncVerificationRequest(context.Background(), request)
err = s.m.SyncVerificationRequest(context.Background(), request, s.m.dispatchMessage)
s.Require().NoError(err)
// Wait for the message to reach its destination
@ -160,7 +160,7 @@ func (s *MessengerSyncVerificationRequests) TestSyncTrust() {
s.Require().NoError(err)
// sync
err = s.m.SyncTrustedUser(context.Background(), "0x01", verification.TrustStatusTRUSTED)
err = s.m.SyncTrustedUser(context.Background(), "0x01", verification.TrustStatusTRUSTED, s.m.dispatchMessage)
s.Require().NoError(err)
// Wait for the message to reach its destination

View File

@ -146,7 +146,7 @@ func (s *MessengerSyncWalletSuite) TestSyncWallets() {
s.Len(acc1, 2, "Must have 2 accounts")
// Trigger's a sync between devices
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image")
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil)
s.Require().NoError(err)
err = tt.RetryWithBackOff(func() error {

View File

@ -2343,6 +2343,117 @@ func (m *BackedUpProfile) GetPictures() []*SyncProfilePicture {
return nil
}
type RawMessage struct {
Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
MessageType ApplicationMetadataMessage_Type `protobuf:"varint,2,opt,name=messageType,proto3,enum=protobuf.ApplicationMetadataMessage_Type" json:"messageType,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RawMessage) Reset() { *m = RawMessage{} }
func (m *RawMessage) String() string { return proto.CompactTextString(m) }
func (*RawMessage) ProtoMessage() {}
func (*RawMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{29}
}
func (m *RawMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RawMessage.Unmarshal(m, b)
}
func (m *RawMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RawMessage.Marshal(b, m, deterministic)
}
func (m *RawMessage) XXX_Merge(src proto.Message) {
xxx_messageInfo_RawMessage.Merge(m, src)
}
func (m *RawMessage) XXX_Size() int {
return xxx_messageInfo_RawMessage.Size(m)
}
func (m *RawMessage) XXX_DiscardUnknown() {
xxx_messageInfo_RawMessage.DiscardUnknown(m)
}
var xxx_messageInfo_RawMessage proto.InternalMessageInfo
func (m *RawMessage) GetPayload() []byte {
if m != nil {
return m.Payload
}
return nil
}
func (m *RawMessage) GetMessageType() ApplicationMetadataMessage_Type {
if m != nil {
return m.MessageType
}
return ApplicationMetadataMessage_UNKNOWN
}
type SyncRawMessage struct {
RawMessages []*RawMessage `protobuf:"bytes,1,rep,name=rawMessages,proto3" json:"rawMessages,omitempty"`
// we need these to be able to login
SubAccountsJsonBytes []byte `protobuf:"bytes,2,opt,name=subAccountsJsonBytes,proto3" json:"subAccountsJsonBytes,omitempty"`
SettingsJsonBytes []byte `protobuf:"bytes,3,opt,name=settingsJsonBytes,proto3" json:"settingsJsonBytes,omitempty"`
NodeConfigJsonBytes []byte `protobuf:"bytes,4,opt,name=nodeConfigJsonBytes,proto3" json:"nodeConfigJsonBytes,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SyncRawMessage) Reset() { *m = SyncRawMessage{} }
func (m *SyncRawMessage) String() string { return proto.CompactTextString(m) }
func (*SyncRawMessage) ProtoMessage() {}
func (*SyncRawMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_d61ab7221f0b5518, []int{30}
}
func (m *SyncRawMessage) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SyncRawMessage.Unmarshal(m, b)
}
func (m *SyncRawMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SyncRawMessage.Marshal(b, m, deterministic)
}
func (m *SyncRawMessage) XXX_Merge(src proto.Message) {
xxx_messageInfo_SyncRawMessage.Merge(m, src)
}
func (m *SyncRawMessage) XXX_Size() int {
return xxx_messageInfo_SyncRawMessage.Size(m)
}
func (m *SyncRawMessage) XXX_DiscardUnknown() {
xxx_messageInfo_SyncRawMessage.DiscardUnknown(m)
}
var xxx_messageInfo_SyncRawMessage proto.InternalMessageInfo
func (m *SyncRawMessage) GetRawMessages() []*RawMessage {
if m != nil {
return m.RawMessages
}
return nil
}
func (m *SyncRawMessage) GetSubAccountsJsonBytes() []byte {
if m != nil {
return m.SubAccountsJsonBytes
}
return nil
}
func (m *SyncRawMessage) GetSettingsJsonBytes() []byte {
if m != nil {
return m.SettingsJsonBytes
}
return nil
}
func (m *SyncRawMessage) GetNodeConfigJsonBytes() []byte {
if m != nil {
return m.NodeConfigJsonBytes
}
return nil
}
func init() {
proto.RegisterEnum("protobuf.SyncTrustedUser_TrustStatus", SyncTrustedUser_TrustStatus_name, SyncTrustedUser_TrustStatus_value)
proto.RegisterEnum("protobuf.SyncVerificationRequest_VerificationStatus", SyncVerificationRequest_VerificationStatus_name, SyncVerificationRequest_VerificationStatus_value)
@ -2379,6 +2490,8 @@ func init() {
proto.RegisterType((*SyncVerificationRequest)(nil), "protobuf.SyncVerificationRequest")
proto.RegisterType((*SyncContactRequestDecision)(nil), "protobuf.SyncContactRequestDecision")
proto.RegisterType((*BackedUpProfile)(nil), "protobuf.BackedUpProfile")
proto.RegisterType((*RawMessage)(nil), "protobuf.RawMessage")
proto.RegisterType((*SyncRawMessage)(nil), "protobuf.SyncRawMessage")
}
func init() {
@ -2386,141 +2499,150 @@ func init() {
}
var fileDescriptor_d61ab7221f0b5518 = []byte{
// 2174 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0x51, 0x73, 0x1b, 0x49,
0x11, 0xbe, 0xd5, 0x2a, 0x96, 0xd4, 0x92, 0x6d, 0xdd, 0xe4, 0x2e, 0x51, 0x9c, 0xa4, 0xe2, 0x6c,
0x48, 0x5d, 0x1e, 0x0e, 0x1f, 0x95, 0x40, 0x1d, 0x5c, 0xee, 0x8a, 0x53, 0x64, 0x73, 0xd1, 0x25,
0x51, 0x5c, 0x63, 0x3b, 0x01, 0x8a, 0xaa, 0xad, 0xf1, 0xee, 0xc4, 0x1a, 0xbc, 0xda, 0x5d, 0x76,
0x46, 0x0e, 0xcb, 0x0f, 0xe0, 0x07, 0xf0, 0xc2, 0xeb, 0xbd, 0xf3, 0x46, 0xd5, 0xf1, 0xc4, 0x0f,
0xe0, 0x8d, 0x07, 0x5e, 0xa8, 0x82, 0xe2, 0x07, 0xf0, 0x2b, 0xa8, 0xe9, 0x99, 0x95, 0x76, 0x65,
0xc9, 0x38, 0xc5, 0x13, 0x4f, 0x9a, 0xee, 0xe9, 0xee, 0xed, 0xe9, 0xee, 0xe9, 0xfe, 0x46, 0xb0,
0x9e, 0x32, 0x91, 0x89, 0xf8, 0x64, 0x27, 0xcd, 0x12, 0x95, 0x90, 0x26, 0xfe, 0x1c, 0x4f, 0xdf,
0x6c, 0x5d, 0x95, 0x79, 0x1c, 0xf8, 0x92, 0x2b, 0x25, 0xe2, 0x13, 0x69, 0xb6, 0x3d, 0x06, 0x37,
0x7f, 0xc2, 0x55, 0x30, 0x16, 0xf1, 0xc9, 0x13, 0x16, 0x9c, 0xf2, 0xf0, 0x28, 0xdd, 0x65, 0x8a,
0xed, 0x72, 0xc5, 0x44, 0x24, 0xc9, 0x1d, 0x68, 0x87, 0x4c, 0x31, 0x3f, 0x9e, 0x4e, 0x8e, 0x79,
0xd6, 0x73, 0xb6, 0x9d, 0x07, 0xeb, 0x14, 0x34, 0x6b, 0x84, 0x1c, 0x72, 0x17, 0x3a, 0x2a, 0x51,
0x2c, 0x2a, 0x24, 0x6a, 0x28, 0xd1, 0x46, 0x9e, 0x11, 0xf1, 0xfe, 0x5e, 0x87, 0x35, 0x6d, 0x7b,
0x9a, 0x92, 0x0f, 0xe0, 0x4a, 0x10, 0x25, 0xc1, 0x29, 0x1a, 0xaa, 0x53, 0x43, 0x90, 0x0d, 0xa8,
0x89, 0x10, 0x35, 0x5b, 0xb4, 0x26, 0x42, 0xf2, 0x63, 0x68, 0x06, 0x49, 0xac, 0x58, 0xa0, 0x64,
0xcf, 0xdd, 0x76, 0x1f, 0xb4, 0x1f, 0xde, 0xdb, 0x29, 0x4e, 0xb1, 0x73, 0x90, 0xc7, 0xc1, 0x30,
0x96, 0x8a, 0x45, 0x11, 0x53, 0x22, 0x89, 0x07, 0x46, 0xf2, 0xd5, 0x43, 0x3a, 0x53, 0x22, 0x3f,
0x82, 0x76, 0x90, 0x4c, 0x26, 0xd3, 0x58, 0x28, 0xc1, 0x65, 0xaf, 0x8e, 0x36, 0xae, 0x57, 0x6d,
0x0c, 0xac, 0x40, 0x4e, 0xcb, 0xb2, 0xe4, 0x25, 0x6c, 0x16, 0x66, 0x6c, 0x0c, 0x7a, 0x57, 0xb6,
0x9d, 0x07, 0xed, 0x87, 0xf7, 0xe7, 0xea, 0x17, 0x04, 0x8c, 0x2e, 0x6a, 0x93, 0x23, 0x20, 0x25,
0xfb, 0x85, 0xcd, 0xb5, 0x77, 0xb1, 0xb9, 0xc4, 0x00, 0x79, 0x04, 0x8d, 0x34, 0x4b, 0xde, 0x88,
0x88, 0xf7, 0x1a, 0x68, 0xeb, 0xc6, 0xdc, 0x56, 0x61, 0x63, 0xdf, 0x08, 0xd0, 0x42, 0x92, 0xbc,
0x80, 0x0d, 0xbb, 0x2c, 0xfc, 0x68, 0xbe, 0x8b, 0x1f, 0x0b, 0xca, 0xe4, 0x13, 0x68, 0xd8, 0x6a,
0xea, 0xb5, 0xd0, 0xce, 0x87, 0xd5, 0x10, 0x1f, 0x98, 0x4d, 0x5a, 0x48, 0xe9, 0xe0, 0x16, 0xe5,
0x57, 0x38, 0x00, 0xef, 0x14, 0xdc, 0x05, 0x6d, 0xef, 0xcf, 0x75, 0xe8, 0xbc, 0x98, 0x46, 0x4a,
0xf4, 0x83, 0x20, 0x99, 0xc6, 0x8a, 0x10, 0xa8, 0xc7, 0x6c, 0xc2, 0xb1, 0xbe, 0x5a, 0x14, 0xd7,
0xe4, 0x16, 0xb4, 0x94, 0x98, 0x70, 0xa9, 0xd8, 0x24, 0xc5, 0x2a, 0x73, 0xe9, 0x9c, 0xa1, 0x77,
0x45, 0xc8, 0x63, 0x25, 0x82, 0x24, 0xee, 0xb9, 0xa8, 0x36, 0x67, 0x90, 0x2f, 0x01, 0x82, 0x24,
0x4a, 0x32, 0x7f, 0xcc, 0xe4, 0xd8, 0x16, 0xd2, 0xdd, 0xb9, 0xb3, 0xe5, 0x6f, 0xef, 0x0c, 0x92,
0x28, 0x99, 0x66, 0x4f, 0x99, 0x1c, 0xd3, 0x16, 0x2a, 0xe9, 0x25, 0xe9, 0x41, 0x03, 0x89, 0x61,
0x88, 0x85, 0xe4, 0xd2, 0x82, 0x24, 0x1f, 0xc1, 0xe6, 0x29, 0xcf, 0x03, 0x96, 0x85, 0xbe, 0xbd,
0xb2, 0x58, 0x16, 0x2d, 0xba, 0x61, 0xd9, 0xfb, 0x86, 0x4b, 0xae, 0x43, 0xe3, 0x94, 0xe7, 0xfe,
0x54, 0x84, 0x98, 0xeb, 0x16, 0x5d, 0x3b, 0xe5, 0xf9, 0x91, 0x08, 0xc9, 0xe7, 0xb0, 0x26, 0x26,
0xec, 0x84, 0xeb, 0x3c, 0x6a, 0xcf, 0xbe, 0xb3, 0xc2, 0xb3, 0x21, 0x9e, 0x47, 0xe5, 0x43, 0x2d,
0x4c, 0xad, 0xce, 0x96, 0x07, 0x30, 0x77, 0x59, 0x5f, 0x4d, 0x11, 0x87, 0xfc, 0xd7, 0x3d, 0x67,
0xdb, 0x7d, 0xe0, 0x52, 0x43, 0x6c, 0xfd, 0xc3, 0x81, 0xf5, 0x8a, 0x76, 0xd9, 0x19, 0xa7, 0xe2,
0x4c, 0x11, 0xfa, 0x5a, 0x29, 0xf4, 0x3d, 0x68, 0xa4, 0x2c, 0x8f, 0x12, 0x16, 0x62, 0x68, 0x3b,
0xb4, 0x20, 0xf5, 0xe7, 0xde, 0x8a, 0x50, 0xe9, 0x98, 0xea, 0xa0, 0x18, 0x82, 0x5c, 0x83, 0xb5,
0x31, 0x17, 0x27, 0x63, 0x65, 0x63, 0x65, 0x29, 0xb2, 0x05, 0x4d, 0x5d, 0x78, 0x52, 0xfc, 0x86,
0x63, 0x8c, 0x5c, 0x3a, 0xa3, 0xc9, 0x3d, 0x58, 0xcf, 0x70, 0xe5, 0x2b, 0x96, 0x9d, 0x70, 0x85,
0x31, 0x72, 0x69, 0xc7, 0x30, 0x0f, 0x91, 0x37, 0x6f, 0x3c, 0xcd, 0x52, 0xe3, 0xf1, 0xfe, 0xe6,
0xc0, 0xd5, 0xe7, 0x49, 0xc0, 0x22, 0x1b, 0xe9, 0x7d, 0xeb, 0xdc, 0x0f, 0xa0, 0x7e, 0xca, 0x73,
0x89, 0xa1, 0xa8, 0xe4, 0x7b, 0x89, 0xf0, 0xce, 0x33, 0x9e, 0x53, 0x14, 0x27, 0x9f, 0x41, 0x67,
0xa2, 0xc3, 0xce, 0x4c, 0xd8, 0x31, 0x12, 0xed, 0x87, 0xd7, 0x96, 0x27, 0x85, 0x56, 0x64, 0xf5,
0x09, 0x53, 0x26, 0xe5, 0xdb, 0x24, 0x0b, 0x6d, 0x15, 0xce, 0xe8, 0xad, 0xef, 0x82, 0xfb, 0x8c,
0xe7, 0x4b, 0x6b, 0x9b, 0x40, 0x5d, 0x37, 0x63, 0xfc, 0x54, 0x87, 0xe2, 0xda, 0xfb, 0xad, 0x03,
0x5d, 0xed, 0x63, 0xb9, 0x4b, 0xae, 0xe8, 0xbc, 0x1f, 0xc1, 0xa6, 0x28, 0x49, 0xf9, 0xb3, 0x36,
0xbc, 0x51, 0x66, 0x0f, 0x43, 0x9c, 0x03, 0xfc, 0x4c, 0x04, 0xdc, 0x57, 0x79, 0xca, 0xad, 0x87,
0x60, 0x58, 0x87, 0x79, 0xca, 0x67, 0xce, 0xd5, 0xe7, 0xce, 0x79, 0xff, 0x76, 0xe0, 0xfa, 0x8a,
0x76, 0x7d, 0xc9, 0x49, 0x70, 0x0f, 0xd6, 0x6d, 0xcf, 0xf1, 0xb1, 0x68, 0xed, 0x87, 0x3b, 0x96,
0x69, 0x2a, 0xf2, 0x06, 0x34, 0x79, 0x2c, 0xfd, 0xd2, 0xe7, 0x1b, 0x3c, 0x96, 0x23, 0x1d, 0x9e,
0xbb, 0xd0, 0x89, 0x98, 0x54, 0xfe, 0x34, 0x0d, 0x99, 0xe2, 0xe6, 0x06, 0xd6, 0x69, 0x5b, 0xf3,
0x8e, 0x0c, 0x4b, 0x9f, 0x4c, 0xe6, 0x52, 0xf1, 0x89, 0xaf, 0xd8, 0x89, 0x6e, 0xcc, 0xae, 0x3e,
0x99, 0x61, 0x1d, 0xb2, 0x13, 0x49, 0xee, 0xc3, 0x46, 0xa4, 0xd3, 0xee, 0xc7, 0x22, 0x38, 0xc5,
0x8f, 0x98, 0x4b, 0xb8, 0x8e, 0xdc, 0x91, 0x65, 0x7a, 0xff, 0x72, 0xe1, 0xc6, 0xca, 0xd9, 0x44,
0xbe, 0x07, 0x1f, 0x94, 0x1d, 0xf1, 0x51, 0x37, 0xca, 0xed, 0xe9, 0x49, 0xc9, 0xa1, 0xe7, 0x66,
0xe7, 0xff, 0x38, 0x14, 0x3a, 0xb7, 0x2c, 0x0c, 0x79, 0x88, 0x53, 0xa1, 0x49, 0x0d, 0xa1, 0x7b,
0xc1, 0xb1, 0x4e, 0x32, 0x0f, 0xb1, 0xe9, 0x37, 0x69, 0x41, 0x6a, 0xf9, 0xc9, 0x54, 0xfb, 0xd4,
0x36, 0xf2, 0x48, 0x68, 0xf9, 0x8c, 0x4f, 0x92, 0x33, 0x1e, 0xf6, 0x3a, 0x46, 0xde, 0x92, 0x64,
0x1b, 0x3a, 0x63, 0x26, 0x7d, 0x34, 0xeb, 0x4f, 0x65, 0x6f, 0x1d, 0xb7, 0x61, 0xcc, 0x64, 0x5f,
0xb3, 0x8e, 0xf4, 0x64, 0xba, 0x7a, 0xc6, 0x33, 0xf1, 0x46, 0x04, 0xa6, 0xae, 0xa5, 0x62, 0x6a,
0x2a, 0x7b, 0x1b, 0xd8, 0x19, 0x48, 0x79, 0xeb, 0x00, 0x77, 0x10, 0xc6, 0x64, 0x53, 0xa9, 0x0a,
0xc9, 0x4d, 0x94, 0x6c, 0x23, 0xcf, 0x88, 0x78, 0x6f, 0xcf, 0x17, 0x73, 0x31, 0x75, 0x96, 0x17,
0xf3, 0xb9, 0x8c, 0xd5, 0x96, 0x64, 0x6c, 0x31, 0x2d, 0xee, 0xb9, 0xb4, 0x78, 0x4f, 0x60, 0x6b,
0xf1, 0xc3, 0xfb, 0xd3, 0xe3, 0x48, 0x04, 0x83, 0x31, 0xbb, 0xe4, 0x45, 0xf2, 0xbe, 0x75, 0x61,
0xbd, 0x82, 0x7a, 0xfe, 0xab, 0x5e, 0x07, 0xab, 0xee, 0x0e, 0xb4, 0xd3, 0x4c, 0x9c, 0x31, 0xc5,
0xfd, 0x53, 0x9e, 0xdb, 0x26, 0x0e, 0x96, 0xa5, 0x9b, 0xd2, 0xb6, 0x6e, 0x0c, 0x32, 0xc8, 0x44,
0xaa, 0xfd, 0xc2, 0xa2, 0xeb, 0xd0, 0x32, 0x4b, 0xf7, 0xf4, 0x5f, 0x26, 0x22, 0xb6, 0x25, 0xd7,
0xa4, 0x96, 0xd2, 0x1d, 0xcf, 0x24, 0x82, 0x87, 0xd8, 0xd3, 0x9b, 0x74, 0x46, 0xcf, 0x2b, 0xa2,
0x51, 0xae, 0x88, 0x97, 0xd0, 0xcd, 0xf8, 0xaf, 0xa6, 0x5c, 0x2a, 0xe9, 0xab, 0xc4, 0xd7, 0x76,
0xec, 0xe0, 0xbb, 0xbf, 0x0a, 0xdb, 0x59, 0xf1, 0xc3, 0xe4, 0xeb, 0x44, 0xc4, 0x74, 0x23, 0xab,
0xd0, 0xe4, 0x31, 0x34, 0x0b, 0x44, 0x61, 0x11, 0xcc, 0x9d, 0x15, 0x86, 0x2c, 0x94, 0x91, 0x74,
0xa6, 0xa0, 0x81, 0x03, 0x8f, 0x83, 0x2c, 0x4f, 0xd5, 0xac, 0xa2, 0xe7, 0x0c, 0xbd, 0x2b, 0x53,
0x1e, 0x28, 0x36, 0xaf, 0xeb, 0x39, 0x43, 0xf7, 0x5d, 0x2b, 0xaa, 0xab, 0x13, 0x67, 0x4d, 0x07,
0x23, 0xb7, 0x31, 0x67, 0x3f, 0xe3, 0xb9, 0xf4, 0xfe, 0xea, 0xc0, 0xcd, 0x0b, 0x4e, 0x64, 0xf3,
0xe5, 0xcc, 0xf2, 0x75, 0x1b, 0x20, 0xc5, 0xda, 0xc0, 0x74, 0x99, 0xfc, 0xb7, 0x0c, 0x47, 0x67,
0x6b, 0x96, 0x74, 0xb7, 0x9c, 0xf4, 0x0b, 0xba, 0xc6, 0x75, 0x68, 0x04, 0x63, 0xa6, 0xf4, 0x60,
0xb8, 0x62, 0xa6, 0xbd, 0x26, 0x87, 0xa1, 0xae, 0xdb, 0x02, 0x95, 0xe6, 0x7a, 0x77, 0xcd, 0x24,
0x7e, 0xc6, 0x1b, 0x62, 0x12, 0xf5, 0x6d, 0x32, 0x4d, 0xa2, 0x4e, 0x0d, 0xe1, 0xfd, 0xae, 0x06,
0xdd, 0xc5, 0x72, 0x26, 0x5f, 0x94, 0x10, 0xff, 0xb9, 0xa1, 0xbb, 0xa2, 0xab, 0x96, 0xf0, 0xfe,
0x57, 0xd0, 0xb1, 0xa7, 0xd6, 0xde, 0xc9, 0x5e, 0x6d, 0x11, 0x0d, 0xad, 0xbe, 0x3f, 0xb4, 0x9d,
0xce, 0xd6, 0x92, 0x3c, 0x86, 0x46, 0x31, 0xbc, 0x5d, 0xac, 0x87, 0x0b, 0xdc, 0x28, 0xe6, 0x78,
0xa1, 0xf1, 0x3f, 0xbc, 0x3a, 0xbc, 0x4f, 0x61, 0x13, 0x77, 0xb5, 0x43, 0xb6, 0xc9, 0x5d, 0xee,
0x5e, 0x7f, 0x0e, 0x1f, 0x14, 0x8a, 0x2f, 0xb8, 0x94, 0x1a, 0xd7, 0x51, 0xce, 0x2e, 0xab, 0xfd,
0x25, 0x5c, 0xd3, 0xda, 0xfd, 0x40, 0x89, 0x33, 0xa1, 0xf2, 0x01, 0x8f, 0x15, 0xcf, 0x2e, 0xd0,
0xef, 0x82, 0x2b, 0x42, 0x13, 0xde, 0x0e, 0xd5, 0x4b, 0x6f, 0xd7, 0xf4, 0xa6, 0xaa, 0x85, 0x7e,
0x10, 0x70, 0xbc, 0x04, 0x97, 0xb5, 0xb2, 0x67, 0x8a, 0xbc, 0x6a, 0x65, 0x57, 0xc8, 0x89, 0x90,
0xf2, 0x1d, 0xcc, 0x7c, 0xe3, 0x40, 0x47, 0xdb, 0x79, 0x92, 0x24, 0xa7, 0x13, 0x96, 0x9d, 0xae,
0x56, 0x9c, 0x66, 0x91, 0x0d, 0x83, 0x5e, 0xce, 0xc0, 0x8b, 0x5b, 0x42, 0x56, 0x37, 0xa1, 0x85,
0x5d, 0xdb, 0xd7, 0xb2, 0xe6, 0x56, 0x34, 0x91, 0x71, 0x94, 0x45, 0xe5, 0xd9, 0x74, 0xa5, 0x3a,
0x9b, 0x6e, 0x03, 0x84, 0x3c, 0xe2, 0x7a, 0xc6, 0x33, 0x85, 0xb7, 0xa2, 0x4e, 0x5b, 0x96, 0xd3,
0x57, 0xde, 0xd7, 0xa6, 0xf8, 0x07, 0x11, 0x67, 0xd9, 0x53, 0x21, 0x55, 0x92, 0xe5, 0xe5, 0x3b,
0xe6, 0x54, 0xee, 0xd8, 0x6d, 0x80, 0x40, 0x0b, 0x1a, 0x5b, 0x35, 0x63, 0xcb, 0x72, 0xfa, 0xca,
0xfb, 0x8b, 0x03, 0x44, 0x1b, 0xb3, 0xcf, 0xbc, 0x7d, 0x11, 0xa8, 0x69, 0xc6, 0x97, 0xc2, 0xc4,
0x12, 0x0e, 0xaf, 0xad, 0xc0, 0xe1, 0x2e, 0x3e, 0xdc, 0xcf, 0xe1, 0xf0, 0x3a, 0xb2, 0x0b, 0x1c,
0x7e, 0x13, 0x5a, 0x38, 0xcf, 0x10, 0x88, 0x5f, 0xc1, 0x2d, 0x04, 0xe2, 0x07, 0x4b, 0x81, 0xf8,
0x1a, 0x0a, 0xac, 0x00, 0xe2, 0x8d, 0x32, 0x10, 0x1f, 0xc3, 0xd5, 0xf3, 0x27, 0x91, 0xab, 0xdf,
0x1a, 0x3f, 0x84, 0x66, 0x6a, 0x85, 0xec, 0x65, 0xbf, 0x55, 0xbd, 0x67, 0x55, 0x4b, 0x74, 0x26,
0xed, 0xfd, 0xa1, 0x06, 0xef, 0x6b, 0x81, 0xd7, 0x2c, 0x8a, 0xb8, 0xba, 0x78, 0x80, 0xf7, 0xa0,
0xc1, 0xc2, 0x30, 0xe3, 0x52, 0x16, 0x51, 0xb3, 0xa4, 0x8e, 0xcf, 0x5b, 0x34, 0x80, 0x61, 0x6b,
0x52, 0x4b, 0xe9, 0xd8, 0xeb, 0xdc, 0x61, 0xd4, 0x9a, 0x14, 0xd7, 0x9a, 0x87, 0x98, 0xd9, 0xf4,
0x4f, 0x5c, 0x6b, 0xcb, 0x3a, 0xf7, 0x1a, 0x14, 0x98, 0x27, 0x5f, 0x41, 0x6a, 0xe9, 0x94, 0xa9,
0xb1, 0x05, 0x56, 0xb8, 0xd6, 0xb3, 0x64, 0xd6, 0xc2, 0xf1, 0x01, 0xd3, 0x29, 0xf7, 0xf4, 0x22,
0xdf, 0xad, 0x52, 0xbe, 0xf5, 0x79, 0xf4, 0x2b, 0x13, 0xe7, 0x52, 0x8b, 0x1a, 0x02, 0xb3, 0x2a,
0xc2, 0x90, 0xc7, 0x76, 0x20, 0x59, 0x6a, 0x35, 0xd2, 0xf2, 0x5e, 0x98, 0x0a, 0xab, 0x04, 0x4b,
0x92, 0x4f, 0xa1, 0x69, 0x7b, 0x5e, 0xd1, 0xad, 0x6f, 0x56, 0xa3, 0x5f, 0x91, 0xa7, 0x33, 0x61,
0xef, 0x4f, 0x8e, 0x29, 0xff, 0x03, 0x76, 0xc6, 0xc3, 0xbe, 0x8d, 0x65, 0x29, 0xca, 0x4e, 0x35,
0xca, 0xcb, 0x5e, 0x94, 0xb7, 0xa0, 0xf5, 0x86, 0x9d, 0x25, 0xd3, 0x4c, 0x28, 0x6e, 0x83, 0x3f,
0x67, 0xe8, 0x49, 0x16, 0x8c, 0x99, 0xc0, 0x87, 0x4c, 0x1d, 0x53, 0xd9, 0x40, 0x7a, 0x18, 0x5e,
0x70, 0x65, 0xef, 0x42, 0xc7, 0xa0, 0x2f, 0xbf, 0x5c, 0x99, 0x6d, 0xc3, 0x1b, 0x60, 0x7d, 0xfe,
0xde, 0x81, 0x0f, 0x97, 0xe2, 0x81, 0x15, 0x95, 0xb3, 0x38, 0x1d, 0xcd, 0x09, 0x2a, 0xd3, 0x71,
0x0f, 0xee, 0x8c, 0x4d, 0x03, 0xf0, 0x59, 0x16, 0x8c, 0xc5, 0x19, 0xf7, 0xe5, 0x34, 0x4d, 0x93,
0x4c, 0xf9, 0x3c, 0x66, 0xc7, 0x91, 0xc5, 0x82, 0x4d, 0x7a, 0xcb, 0x8a, 0xf5, 0x8d, 0xd4, 0x81,
0x11, 0xda, 0x33, 0x32, 0xde, 0x1f, 0x1d, 0x33, 0x3a, 0x0e, 0x35, 0x52, 0xd5, 0xd8, 0x97, 0x67,
0x97, 0x7c, 0x5b, 0x7d, 0x01, 0x6b, 0x16, 0xec, 0xea, 0xef, 0x6c, 0x2c, 0x62, 0xa8, 0x92, 0xc1,
0x9d, 0xc3, 0x39, 0x0c, 0xa6, 0x56, 0xc9, 0xfb, 0x0c, 0xda, 0x25, 0x36, 0x69, 0x43, 0xe3, 0x68,
0xf4, 0x6c, 0xf4, 0xf2, 0xf5, 0xa8, 0xfb, 0x9e, 0x26, 0x0e, 0xe9, 0xd1, 0xc1, 0xe1, 0xde, 0x6e,
0xd7, 0x21, 0xef, 0xc3, 0xfa, 0xd1, 0x08, 0xc9, 0xd7, 0x2f, 0xe9, 0xe1, 0xd3, 0x9f, 0x75, 0x6b,
0xde, 0x37, 0xae, 0xc1, 0xd2, 0xaf, 0x4a, 0x40, 0xdc, 0x02, 0x9b, 0x15, 0xce, 0x13, 0xa8, 0xbf,
0xc9, 0x92, 0x49, 0x51, 0x0a, 0x7a, 0xad, 0x0f, 0xa4, 0x12, 0xdb, 0xb3, 0x6b, 0x2a, 0xd1, 0xa5,
0x11, 0x8c, 0x75, 0xe5, 0xc5, 0x27, 0x05, 0x8e, 0x99, 0x33, 0x74, 0x4a, 0x2c, 0xfa, 0x33, 0xed,
0xd4, 0xbe, 0x7f, 0x66, 0xbc, 0x3e, 0xbe, 0xc1, 0x33, 0x2e, 0xd3, 0x24, 0x96, 0xc5, 0xb5, 0x9c,
0xd1, 0xba, 0x17, 0x67, 0x3c, 0x8d, 0x84, 0x51, 0x36, 0x25, 0xd2, 0xb2, 0x9c, 0xbe, 0x22, 0x7c,
0xf9, 0x83, 0xa3, 0x89, 0x91, 0xfd, 0x7e, 0x35, 0xb2, 0x4b, 0x4e, 0xbd, 0xf3, 0xea, 0xdc, 0x93,
0x64, 0xe9, 0x33, 0xc5, 0xe4, 0xb0, 0x35, 0x1b, 0xe0, 0x3f, 0x05, 0x72, 0x5e, 0xf3, 0x5c, 0x2e,
0xf6, 0xf7, 0x46, 0xbb, 0xc3, 0xd1, 0x57, 0x5d, 0x87, 0x74, 0xa0, 0xd9, 0x1f, 0x0c, 0xf6, 0xf6,
0x75, 0x66, 0x6a, 0x9a, 0xda, 0xdd, 0x1b, 0x3c, 0x1f, 0x8e, 0xf6, 0x76, 0xbb, 0xae, 0xa6, 0x06,
0xfd, 0xd1, 0x60, 0xef, 0xf9, 0xde, 0x6e, 0xb7, 0xee, 0xfd, 0xd3, 0x31, 0x93, 0xbd, 0x00, 0x5b,
0xc6, 0xcf, 0x5d, 0x1e, 0x08, 0xb9, 0xfa, 0xef, 0x84, 0x5b, 0xd0, 0xb2, 0xf1, 0x1c, 0x16, 0x95,
0x36, 0x67, 0x90, 0x5f, 0xc0, 0x66, 0x68, 0xf5, 0xfd, 0x4a, 0xe5, 0x3d, 0x5a, 0xc4, 0x48, 0xcb,
0x3e, 0xb9, 0x53, 0x2c, 0x6c, 0x78, 0x36, 0xc2, 0x0a, 0xed, 0x7d, 0x0c, 0x1b, 0x55, 0x89, 0xca,
0x61, 0xdf, 0xab, 0x1c, 0xd6, 0xf1, 0xbe, 0x75, 0x60, 0x73, 0xe1, 0x6f, 0xd2, 0xd5, 0xd3, 0xe6,
0x2e, 0x74, 0x42, 0x21, 0xd3, 0x88, 0xe5, 0x7e, 0xa9, 0x1f, 0xb5, 0x2d, 0x0f, 0x71, 0xf2, 0xc7,
0x40, 0xca, 0x22, 0x7e, 0x19, 0x65, 0x77, 0x4b, 0x82, 0xd8, 0x4e, 0x2a, 0xe3, 0xab, 0xfe, 0x2e,
0xe3, 0xeb, 0xc9, 0xfa, 0xcf, 0xdb, 0x3b, 0x9f, 0x3c, 0x2e, 0x64, 0x8f, 0xd7, 0x70, 0xf5, 0xe8,
0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x30, 0x89, 0x4d, 0x75, 0xf4, 0x17, 0x00, 0x00,
// 2306 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x5f, 0x73, 0x1b, 0x49,
0x11, 0xbf, 0x95, 0x14, 0x4b, 0x6a, 0xc9, 0xb2, 0x33, 0xc9, 0x25, 0x8a, 0x93, 0x54, 0x9c, 0x0d,
0xa9, 0x0b, 0x55, 0xc1, 0x77, 0x95, 0x00, 0x07, 0x97, 0xbb, 0xe2, 0x14, 0xd9, 0x5c, 0x9c, 0x3f,
0x8e, 0x6b, 0x6c, 0x27, 0x40, 0x51, 0xb5, 0x35, 0xde, 0x1d, 0x5b, 0x83, 0x57, 0xbb, 0xcb, 0xce,
0xc8, 0x61, 0xf9, 0x00, 0x7c, 0x00, 0x5e, 0x78, 0xbd, 0x77, 0xde, 0xa8, 0x3a, 0x9e, 0xf8, 0x00,
0xbc, 0xf1, 0xc0, 0x0b, 0x55, 0x50, 0x7c, 0x00, 0x3e, 0x05, 0x35, 0x3d, 0xb3, 0xda, 0x5d, 0x59,
0x32, 0x4e, 0xf1, 0x74, 0x4f, 0x9e, 0xee, 0xe9, 0xee, 0xed, 0xe9, 0xee, 0xe9, 0xfe, 0x8d, 0x0c,
0xcb, 0x09, 0x13, 0xa9, 0x88, 0x8e, 0x37, 0x92, 0x34, 0x56, 0x31, 0x69, 0xe1, 0x9f, 0xc3, 0xc9,
0xd1, 0xda, 0x15, 0x99, 0x45, 0xbe, 0x27, 0xb9, 0x52, 0x22, 0x3a, 0x96, 0x66, 0x7b, 0xcd, 0x65,
0x49, 0x12, 0x0a, 0x9f, 0x29, 0x11, 0x47, 0xde, 0x98, 0x2b, 0x16, 0x30, 0xc5, 0xbc, 0x31, 0x97,
0x92, 0x1d, 0x73, 0x23, 0xe3, 0x32, 0xb8, 0xf9, 0x53, 0xae, 0xfc, 0x91, 0x88, 0x8e, 0x9f, 0x32,
0xff, 0x84, 0x07, 0x07, 0xc9, 0x26, 0x53, 0x6c, 0x93, 0x2b, 0x26, 0x42, 0x49, 0xee, 0x40, 0x07,
0x95, 0xa2, 0xc9, 0xf8, 0x90, 0xa7, 0x7d, 0x67, 0xdd, 0x79, 0xb0, 0x4c, 0x41, 0xb3, 0x76, 0x90,
0x43, 0xee, 0x42, 0x57, 0xc5, 0x8a, 0x85, 0xb9, 0x44, 0x0d, 0x25, 0x3a, 0xc8, 0x33, 0x22, 0xee,
0x3f, 0x1a, 0xb0, 0xa4, 0x6d, 0x4f, 0x12, 0x72, 0x15, 0x2e, 0xf9, 0x61, 0xec, 0x9f, 0xa0, 0xa1,
0x06, 0x35, 0x04, 0xe9, 0x41, 0x4d, 0x04, 0xa8, 0xd9, 0xa6, 0x35, 0x11, 0x90, 0x9f, 0x40, 0xcb,
0x8f, 0x23, 0xc5, 0x7c, 0x25, 0xfb, 0xf5, 0xf5, 0xfa, 0x83, 0xce, 0xa3, 0x7b, 0x1b, 0xf9, 0x49,
0x37, 0xf6, 0xb2, 0xc8, 0xdf, 0x8e, 0xa4, 0x62, 0x61, 0x88, 0x07, 0x1b, 0x1a, 0xc9, 0x37, 0x8f,
0xe8, 0x54, 0x89, 0xfc, 0x18, 0x3a, 0x7e, 0x3c, 0x1e, 0x4f, 0x22, 0xa1, 0x04, 0x97, 0xfd, 0x06,
0xda, 0xb8, 0x5e, 0xb5, 0x31, 0xb4, 0x02, 0x19, 0x2d, 0xcb, 0x92, 0xd7, 0xb0, 0x92, 0x9b, 0xb1,
0x31, 0xe8, 0x5f, 0x5a, 0x77, 0x1e, 0x74, 0x1e, 0xdd, 0x2f, 0xd4, 0xcf, 0x09, 0x18, 0x9d, 0xd5,
0x26, 0x07, 0x40, 0x4a, 0xf6, 0x73, 0x9b, 0x4b, 0xef, 0x63, 0x73, 0x8e, 0x01, 0xf2, 0x18, 0x9a,
0x49, 0x1a, 0x1f, 0x89, 0x90, 0xf7, 0x9b, 0x68, 0xeb, 0x46, 0x61, 0x2b, 0xb7, 0xb1, 0x6b, 0x04,
0x68, 0x2e, 0x49, 0x5e, 0x41, 0xcf, 0x2e, 0x73, 0x3f, 0x5a, 0xef, 0xe3, 0xc7, 0x8c, 0x32, 0xf9,
0x18, 0x9a, 0xb6, 0xe2, 0xfa, 0x6d, 0xb4, 0xf3, 0x61, 0x35, 0xc4, 0x7b, 0x66, 0x93, 0xe6, 0x52,
0x3a, 0xb8, 0x79, 0x89, 0xe6, 0x0e, 0xc0, 0x7b, 0x05, 0x77, 0x46, 0xdb, 0xfd, 0x4b, 0x03, 0xba,
0xaf, 0x26, 0xa1, 0x12, 0x03, 0xdf, 0x8f, 0x27, 0x91, 0x22, 0x04, 0x1a, 0x11, 0x1b, 0x73, 0xac,
0xaf, 0x36, 0xc5, 0x35, 0xb9, 0x05, 0x6d, 0x25, 0xc6, 0x5c, 0x2a, 0x36, 0x4e, 0xb0, 0xca, 0xea,
0xb4, 0x60, 0xe8, 0x5d, 0x11, 0xf0, 0x48, 0x09, 0x3f, 0x8e, 0xfa, 0x75, 0x54, 0x2b, 0x18, 0xe4,
0x4b, 0x00, 0x3f, 0x0e, 0xe3, 0xd4, 0x1b, 0x31, 0x39, 0xb2, 0x85, 0x74, 0xb7, 0x70, 0xb6, 0xfc,
0xed, 0x8d, 0x61, 0x1c, 0xc6, 0x93, 0xf4, 0x19, 0x93, 0x23, 0xda, 0x46, 0x25, 0xbd, 0x24, 0x7d,
0x68, 0x22, 0xb1, 0x1d, 0x60, 0x21, 0xd5, 0x69, 0x4e, 0x92, 0x8f, 0x60, 0xe5, 0x84, 0x67, 0x3e,
0x4b, 0x03, 0xcf, 0x5e, 0x6b, 0x2c, 0x8b, 0x36, 0xed, 0x59, 0xf6, 0xae, 0xe1, 0x92, 0xeb, 0xd0,
0x3c, 0xe1, 0x99, 0x37, 0x11, 0x01, 0xe6, 0xba, 0x4d, 0x97, 0x4e, 0x78, 0x76, 0x20, 0x02, 0xf2,
0x39, 0x2c, 0x89, 0x31, 0x3b, 0xe6, 0x3a, 0x8f, 0xda, 0xb3, 0xef, 0x2c, 0xf0, 0x6c, 0x1b, 0xcf,
0xa3, 0xb2, 0x6d, 0x2d, 0x4c, 0xad, 0xce, 0x9a, 0x0b, 0x50, 0xb8, 0xac, 0xaf, 0xa6, 0x88, 0x02,
0xfe, 0x9b, 0xbe, 0xb3, 0x5e, 0x7f, 0x50, 0xa7, 0x86, 0x58, 0xfb, 0xa7, 0x03, 0xcb, 0x15, 0xed,
0xb2, 0x33, 0x4e, 0xc5, 0x99, 0x3c, 0xf4, 0xb5, 0x52, 0xe8, 0xfb, 0xd0, 0x4c, 0x58, 0x16, 0xc6,
0x2c, 0xc0, 0xd0, 0x76, 0x69, 0x4e, 0xea, 0xcf, 0xbd, 0x13, 0x81, 0xd2, 0x31, 0xd5, 0x41, 0x31,
0x04, 0xb9, 0x06, 0x4b, 0x23, 0x2e, 0x8e, 0x47, 0xca, 0xc6, 0xca, 0x52, 0x64, 0x0d, 0x5a, 0xba,
0xf0, 0xa4, 0xf8, 0x2d, 0xc7, 0x18, 0xd5, 0xe9, 0x94, 0x26, 0xf7, 0x60, 0x39, 0xc5, 0x95, 0xa7,
0x58, 0x7a, 0xcc, 0x15, 0xc6, 0xa8, 0x4e, 0xbb, 0x86, 0xb9, 0x8f, 0xbc, 0xa2, 0xf1, 0xb4, 0x4a,
0x8d, 0xc7, 0xfd, 0xbb, 0x03, 0x57, 0x5e, 0xc6, 0x3e, 0x0b, 0x6d, 0xa4, 0x77, 0xad, 0x73, 0x3f,
0x80, 0xc6, 0x09, 0xcf, 0x24, 0x86, 0xa2, 0x92, 0xef, 0x39, 0xc2, 0x1b, 0x2f, 0x78, 0x46, 0x51,
0x9c, 0x7c, 0x06, 0xdd, 0xb1, 0x0e, 0x3b, 0x33, 0x61, 0xc7, 0x48, 0x74, 0x1e, 0x5d, 0x9b, 0x9f,
0x14, 0x5a, 0x91, 0xd5, 0x27, 0x4c, 0x98, 0x94, 0xef, 0xe2, 0x34, 0xb0, 0x55, 0x38, 0xa5, 0xd7,
0xbe, 0x07, 0xf5, 0x17, 0x3c, 0x9b, 0x5b, 0xdb, 0x04, 0x1a, 0xba, 0x19, 0xe3, 0xa7, 0xba, 0x14,
0xd7, 0xee, 0xef, 0x1c, 0x58, 0xd5, 0x3e, 0x96, 0xbb, 0xe4, 0x82, 0xce, 0xfb, 0x11, 0xac, 0x88,
0x92, 0x94, 0x37, 0x6d, 0xc3, 0xbd, 0x32, 0x7b, 0x3b, 0xc0, 0x39, 0xc0, 0x4f, 0x85, 0xcf, 0x3d,
0x95, 0x25, 0xdc, 0x7a, 0x08, 0x86, 0xb5, 0x9f, 0x25, 0x7c, 0xea, 0x5c, 0xa3, 0x70, 0xce, 0xfd,
0x8f, 0x03, 0xd7, 0x17, 0xb4, 0xeb, 0x0b, 0x4e, 0x82, 0x7b, 0xb0, 0x6c, 0x7b, 0x8e, 0x87, 0x45,
0x6b, 0x3f, 0xdc, 0xb5, 0x4c, 0x53, 0x91, 0x37, 0xa0, 0xc5, 0x23, 0xe9, 0x95, 0x3e, 0xdf, 0xe4,
0x91, 0xdc, 0xd1, 0xe1, 0xb9, 0x0b, 0xdd, 0x90, 0x49, 0xe5, 0x4d, 0x92, 0x80, 0x29, 0x6e, 0x6e,
0x60, 0x83, 0x76, 0x34, 0xef, 0xc0, 0xb0, 0xf4, 0xc9, 0x64, 0x26, 0x15, 0x1f, 0x7b, 0x8a, 0x1d,
0xeb, 0xc6, 0x5c, 0xd7, 0x27, 0x33, 0xac, 0x7d, 0x76, 0x2c, 0xc9, 0x7d, 0xe8, 0x85, 0x3a, 0xed,
0x5e, 0x24, 0xfc, 0x13, 0xfc, 0x88, 0xb9, 0x84, 0xcb, 0xc8, 0xdd, 0xb1, 0x4c, 0xf7, 0xdf, 0x75,
0xb8, 0xb1, 0x70, 0x36, 0x91, 0x4f, 0xe0, 0x6a, 0xd9, 0x11, 0x0f, 0x75, 0xc3, 0xcc, 0x9e, 0x9e,
0x94, 0x1c, 0x7a, 0x69, 0x76, 0xbe, 0xc5, 0xa1, 0xd0, 0xb9, 0x65, 0x41, 0xc0, 0x03, 0x9c, 0x0a,
0x2d, 0x6a, 0x08, 0xdd, 0x0b, 0x0e, 0x75, 0x92, 0x79, 0x80, 0x4d, 0xbf, 0x45, 0x73, 0x52, 0xcb,
0x8f, 0x27, 0xda, 0xa7, 0x8e, 0x91, 0x47, 0x42, 0xcb, 0xa7, 0x7c, 0x1c, 0x9f, 0xf2, 0xa0, 0xdf,
0x35, 0xf2, 0x96, 0x24, 0xeb, 0xd0, 0x1d, 0x31, 0xe9, 0xa1, 0x59, 0x6f, 0x22, 0xfb, 0xcb, 0xb8,
0x0d, 0x23, 0x26, 0x07, 0x9a, 0x75, 0xa0, 0x27, 0xd3, 0x95, 0x53, 0x9e, 0x8a, 0xa3, 0x1c, 0xfc,
0x48, 0xc5, 0xd4, 0x44, 0xf6, 0x7b, 0xd8, 0x19, 0x48, 0x79, 0x6b, 0x0f, 0x77, 0x10, 0xc6, 0xa4,
0x13, 0xa9, 0x72, 0xc9, 0x15, 0x94, 0xec, 0x20, 0xcf, 0x88, 0xb8, 0xef, 0xce, 0x16, 0x73, 0x3e,
0x75, 0xe6, 0x17, 0xf3, 0x99, 0x8c, 0xd5, 0xe6, 0x64, 0x6c, 0x36, 0x2d, 0xf5, 0x33, 0x69, 0x71,
0x9f, 0xc2, 0xda, 0xec, 0x87, 0x77, 0x27, 0x87, 0xa1, 0xf0, 0x87, 0x23, 0x76, 0xc1, 0x8b, 0xe4,
0x7e, 0x53, 0x87, 0xe5, 0x0a, 0xea, 0xf9, 0x9f, 0x7a, 0x5d, 0xac, 0xba, 0x3b, 0xd0, 0x49, 0x52,
0x71, 0xca, 0x14, 0xf7, 0x4e, 0x78, 0x66, 0x9b, 0x38, 0x58, 0x96, 0x6e, 0x4a, 0xeb, 0xba, 0x31,
0x48, 0x3f, 0x15, 0x89, 0xf6, 0x0b, 0x8b, 0xae, 0x4b, 0xcb, 0x2c, 0xdd, 0xd3, 0x7f, 0x15, 0x8b,
0xc8, 0x96, 0x5c, 0x8b, 0x5a, 0x4a, 0x77, 0x3c, 0x93, 0x08, 0x1e, 0x60, 0x4f, 0x6f, 0xd1, 0x29,
0x5d, 0x54, 0x44, 0xb3, 0x5c, 0x11, 0xaf, 0x61, 0x35, 0xe5, 0xbf, 0x9e, 0x70, 0xa9, 0xa4, 0xa7,
0x62, 0x4f, 0xdb, 0xb1, 0x83, 0xef, 0xfe, 0x22, 0x6c, 0x67, 0xc5, 0xf7, 0xe3, 0xe7, 0xb1, 0x88,
0x68, 0x2f, 0xad, 0xd0, 0xe4, 0x09, 0xb4, 0x72, 0x44, 0x61, 0x11, 0xcc, 0x9d, 0x05, 0x86, 0x2c,
0x94, 0x91, 0x74, 0xaa, 0xa0, 0x81, 0x03, 0x8f, 0xfc, 0x34, 0x4b, 0xd4, 0xb4, 0xa2, 0x0b, 0x86,
0xde, 0x95, 0x09, 0xf7, 0x15, 0x2b, 0xea, 0xba, 0x60, 0xe8, 0xbe, 0x6b, 0x45, 0x75, 0x75, 0xe2,
0xac, 0xe9, 0x62, 0xe4, 0x7a, 0x05, 0xfb, 0x05, 0xcf, 0xa4, 0xfb, 0x37, 0x07, 0x6e, 0x9e, 0x73,
0x22, 0x9b, 0x2f, 0x67, 0x9a, 0xaf, 0xdb, 0x00, 0x09, 0xd6, 0x06, 0xa6, 0xcb, 0xe4, 0xbf, 0x6d,
0x38, 0x3a, 0x5b, 0xd3, 0xa4, 0xd7, 0xcb, 0x49, 0x3f, 0xa7, 0x6b, 0x5c, 0x87, 0xa6, 0x3f, 0x62,
0x4a, 0x0f, 0x86, 0x4b, 0x66, 0xda, 0x6b, 0x72, 0x3b, 0xd0, 0x75, 0x9b, 0xa3, 0xd2, 0x4c, 0xef,
0x2e, 0x99, 0xc4, 0x4f, 0x79, 0xdb, 0x98, 0x44, 0x7d, 0x9b, 0x4c, 0x93, 0x68, 0x50, 0x43, 0xb8,
0xbf, 0xaf, 0xc1, 0xea, 0x6c, 0x39, 0x93, 0x2f, 0x4a, 0x88, 0xff, 0xcc, 0xd0, 0x5d, 0xd0, 0x55,
0x4b, 0x78, 0xff, 0x2b, 0xe8, 0xda, 0x53, 0x6b, 0xef, 0x64, 0xbf, 0x36, 0x8b, 0x86, 0x16, 0xdf,
0x1f, 0xda, 0x49, 0xa6, 0x6b, 0x49, 0x9e, 0x40, 0x33, 0x1f, 0xde, 0x75, 0xac, 0x87, 0x73, 0xdc,
0xc8, 0xe7, 0x78, 0xae, 0xf1, 0x7f, 0xbc, 0x3a, 0xdc, 0x4f, 0x61, 0x05, 0x77, 0xb5, 0x43, 0xb6,
0xc9, 0x5d, 0xec, 0x5e, 0x7f, 0x0e, 0x57, 0x73, 0xc5, 0x57, 0xe6, 0x5d, 0x27, 0x29, 0x67, 0x17,
0xd5, 0xfe, 0x12, 0xae, 0x69, 0xed, 0x81, 0xaf, 0xc4, 0xa9, 0x50, 0xd9, 0x90, 0x47, 0x8a, 0xa7,
0xe7, 0xe8, 0xaf, 0x42, 0x5d, 0x04, 0x26, 0xbc, 0x5d, 0xaa, 0x97, 0xee, 0xa6, 0xe9, 0x4d, 0x55,
0x0b, 0x03, 0xdf, 0xe7, 0x78, 0x09, 0x2e, 0x6a, 0x65, 0xcb, 0x14, 0x79, 0xd5, 0xca, 0xa6, 0x90,
0x63, 0x21, 0xe5, 0x7b, 0x98, 0xf9, 0xda, 0x81, 0xae, 0xb6, 0xf3, 0x34, 0x8e, 0x4f, 0xc6, 0x2c,
0x3d, 0x59, 0xac, 0x38, 0x49, 0x43, 0x1b, 0x06, 0xbd, 0x9c, 0x82, 0x97, 0x7a, 0x09, 0x59, 0xdd,
0x84, 0x36, 0x76, 0x6d, 0x4f, 0xcb, 0x9a, 0x5b, 0xd1, 0x42, 0xc6, 0x41, 0x1a, 0x96, 0x67, 0xd3,
0xa5, 0xea, 0x6c, 0xba, 0x0d, 0x10, 0xf0, 0x90, 0xeb, 0x19, 0xcf, 0x14, 0xde, 0x8a, 0x06, 0x6d,
0x5b, 0xce, 0x40, 0xb9, 0xcf, 0x4d, 0xf1, 0x0f, 0x43, 0xce, 0xd2, 0x67, 0x42, 0xaa, 0x38, 0xcd,
0xca, 0x77, 0xcc, 0xa9, 0xdc, 0xb1, 0xdb, 0x00, 0xbe, 0x16, 0x34, 0xb6, 0x6a, 0xc6, 0x96, 0xe5,
0x0c, 0x94, 0xfb, 0x57, 0x07, 0x88, 0x36, 0x66, 0x9f, 0x79, 0xbb, 0xc2, 0x57, 0x93, 0x94, 0xcf,
0x85, 0x89, 0x25, 0x1c, 0x5e, 0x5b, 0x80, 0xc3, 0xeb, 0xf8, 0x70, 0x3f, 0x83, 0xc3, 0x1b, 0xc8,
0xce, 0x71, 0xf8, 0x4d, 0x68, 0xe3, 0x3c, 0x43, 0x20, 0x7e, 0x09, 0xb7, 0x10, 0x88, 0xef, 0xcd,
0x05, 0xe2, 0x4b, 0x28, 0xb0, 0x00, 0x88, 0x37, 0xcb, 0x40, 0x7c, 0x04, 0x57, 0xce, 0x9e, 0x44,
0x2e, 0x7e, 0x6b, 0xfc, 0x08, 0x5a, 0x89, 0x15, 0xb2, 0x97, 0xfd, 0x56, 0xf5, 0x9e, 0x55, 0x2d,
0xd1, 0xa9, 0xb4, 0xfb, 0xc7, 0x1a, 0x5c, 0xd6, 0x02, 0x6f, 0x59, 0x18, 0x72, 0x75, 0xfe, 0x00,
0xef, 0x43, 0x93, 0x05, 0x41, 0xca, 0xa5, 0xcc, 0xa3, 0x66, 0x49, 0x1d, 0x9f, 0x77, 0x68, 0x00,
0xc3, 0xd6, 0xa2, 0x96, 0xd2, 0xb1, 0xd7, 0xb9, 0xc3, 0xa8, 0xb5, 0x28, 0xae, 0x35, 0x0f, 0x31,
0xb3, 0xe9, 0x9f, 0xb8, 0xd6, 0x96, 0x75, 0xee, 0x35, 0x28, 0x30, 0x4f, 0xbe, 0x9c, 0xd4, 0xd2,
0x09, 0x53, 0x23, 0x0b, 0xac, 0x70, 0xad, 0x67, 0xc9, 0xb4, 0x85, 0xe3, 0x03, 0xa6, 0x5b, 0xee,
0xe9, 0x79, 0xbe, 0xdb, 0xa5, 0x7c, 0xeb, 0xf3, 0xe8, 0x57, 0x26, 0xce, 0xa5, 0x36, 0x35, 0x04,
0x66, 0x55, 0x04, 0x01, 0x8f, 0xec, 0x40, 0xb2, 0xd4, 0x62, 0xa4, 0xe5, 0xbe, 0x32, 0x15, 0x56,
0x09, 0x96, 0x24, 0x9f, 0x42, 0xcb, 0xf6, 0xbc, 0xbc, 0x5b, 0xdf, 0xac, 0x46, 0xbf, 0x22, 0x4f,
0xa7, 0xc2, 0xee, 0x9f, 0x1d, 0x53, 0xfe, 0x7b, 0xec, 0x94, 0x07, 0x03, 0x1b, 0xcb, 0x52, 0x94,
0x9d, 0x6a, 0x94, 0xe7, 0xbd, 0x28, 0x6f, 0x41, 0xfb, 0x88, 0x9d, 0xc6, 0x93, 0x54, 0x28, 0x6e,
0x83, 0x5f, 0x30, 0xf4, 0x24, 0xf3, 0x47, 0x4c, 0xe0, 0x43, 0xa6, 0x81, 0xa9, 0x6c, 0x22, 0xbd,
0x1d, 0x9c, 0x73, 0x65, 0xef, 0x42, 0xd7, 0xa0, 0x2f, 0xaf, 0x5c, 0x99, 0x1d, 0xc3, 0x1b, 0x62,
0x7d, 0xfe, 0xc1, 0x81, 0x0f, 0xe7, 0xe2, 0x81, 0x05, 0x95, 0x33, 0x3b, 0x1d, 0xcd, 0x09, 0x2a,
0xd3, 0x71, 0x0b, 0xee, 0x8c, 0x4c, 0x03, 0xf0, 0x58, 0xea, 0x8f, 0xc4, 0x29, 0xf7, 0xe4, 0x24,
0x49, 0xe2, 0x54, 0x79, 0x3c, 0x62, 0x87, 0xa1, 0xc5, 0x82, 0x2d, 0x7a, 0xcb, 0x8a, 0x0d, 0x8c,
0xd4, 0x9e, 0x11, 0xda, 0x32, 0x32, 0xee, 0x9f, 0x1c, 0x33, 0x3a, 0xf6, 0x35, 0x52, 0xd5, 0xd8,
0x97, 0xa7, 0x17, 0x7c, 0x5b, 0x7d, 0x01, 0x4b, 0x16, 0xec, 0xea, 0xef, 0xf4, 0x66, 0x31, 0x54,
0xc9, 0xe0, 0xc6, 0x7e, 0x01, 0x83, 0xa9, 0x55, 0x72, 0x3f, 0x83, 0x4e, 0x89, 0x4d, 0x3a, 0xd0,
0x3c, 0xd8, 0x79, 0xb1, 0xf3, 0xfa, 0xed, 0xce, 0xea, 0x07, 0x9a, 0xd8, 0xa7, 0x07, 0x7b, 0xfb,
0x5b, 0x9b, 0xab, 0x0e, 0xb9, 0x0c, 0xcb, 0x07, 0x3b, 0x48, 0xbe, 0x7d, 0x4d, 0xf7, 0x9f, 0xfd,
0x7c, 0xb5, 0xe6, 0x7e, 0x5d, 0x37, 0x58, 0xfa, 0x4d, 0x09, 0x88, 0x5b, 0x60, 0xb3, 0xc0, 0x79,
0x02, 0x8d, 0xa3, 0x34, 0x1e, 0xe7, 0xa5, 0xa0, 0xd7, 0xfa, 0x40, 0x2a, 0xb6, 0x3d, 0xbb, 0xa6,
0x62, 0x5d, 0x1a, 0xfe, 0x48, 0x57, 0x5e, 0x74, 0x9c, 0xe3, 0x98, 0x82, 0xa1, 0x53, 0x62, 0xd1,
0x9f, 0x69, 0xa7, 0xf6, 0xfd, 0x33, 0xe5, 0x0d, 0xf0, 0x0d, 0x9e, 0x72, 0x99, 0xc4, 0x91, 0xcc,
0xaf, 0xe5, 0x94, 0xd6, 0xbd, 0x38, 0xe5, 0x49, 0x28, 0x8c, 0xb2, 0x29, 0x91, 0xb6, 0xe5, 0x0c,
0x14, 0xe1, 0xf3, 0x1f, 0x1c, 0x2d, 0x8c, 0xec, 0xf7, 0xab, 0x91, 0x9d, 0x73, 0xea, 0x8d, 0x37,
0x67, 0x9e, 0x24, 0x73, 0x9f, 0x29, 0x26, 0x87, 0xed, 0xe9, 0x00, 0xff, 0x19, 0x90, 0xb3, 0x9a,
0x67, 0x72, 0xb1, 0xbb, 0xb5, 0xb3, 0xb9, 0xbd, 0xf3, 0xd5, 0xaa, 0x43, 0xba, 0xd0, 0x1a, 0x0c,
0x87, 0x5b, 0xbb, 0x3a, 0x33, 0x35, 0x4d, 0x6d, 0x6e, 0x0d, 0x5f, 0x6e, 0xef, 0x6c, 0x6d, 0xae,
0xd6, 0x35, 0x35, 0x1c, 0xec, 0x0c, 0xb7, 0x5e, 0x6e, 0x6d, 0xae, 0x36, 0xdc, 0x7f, 0x39, 0x66,
0xb2, 0xe7, 0x60, 0xcb, 0xf8, 0xb9, 0xc9, 0x7d, 0x21, 0x17, 0xff, 0x9c, 0x70, 0x0b, 0xda, 0x36,
0x9e, 0xdb, 0x79, 0xa5, 0x15, 0x0c, 0xf2, 0x4b, 0x58, 0x09, 0xac, 0xbe, 0x57, 0xa9, 0xbc, 0xc7,
0xb3, 0x18, 0x69, 0xde, 0x27, 0x37, 0xf2, 0x85, 0x0d, 0x4f, 0x2f, 0xa8, 0xd0, 0xee, 0x43, 0xe8,
0x55, 0x25, 0x2a, 0x87, 0xfd, 0xa0, 0x72, 0x58, 0xc7, 0xfd, 0xc6, 0x81, 0x95, 0x99, 0x9f, 0x49,
0x17, 0x4f, 0x9b, 0xbb, 0xd0, 0x0d, 0x84, 0x4c, 0x42, 0x96, 0x79, 0xa5, 0x7e, 0xd4, 0xb1, 0x3c,
0xc4, 0xc9, 0x0f, 0x81, 0x94, 0x45, 0xbc, 0x32, 0xca, 0x5e, 0x2d, 0x09, 0x62, 0x3b, 0xa9, 0x8c,
0xaf, 0xc6, 0x7b, 0x8d, 0x2f, 0x09, 0x40, 0xd9, 0x3b, 0x0b, 0xf5, 0xca, 0x63, 0xdd, 0xa9, 0x8e,
0xf5, 0x17, 0xd0, 0xb1, 0xbf, 0xf3, 0xef, 0xeb, 0xd9, 0x53, 0xc3, 0x38, 0x7f, 0xb7, 0xf8, 0xc8,
0xa0, 0xf8, 0xcf, 0xc0, 0x2b, 0xfb, 0x8f, 0x01, 0x6b, 0x74, 0x43, 0x2b, 0xd0, 0xb2, 0xb6, 0xae,
0x85, 0x9e, 0xf6, 0xaa, 0xf4, 0xe5, 0x1f, 0x42, 0x27, 0x9d, 0x52, 0xf9, 0x14, 0xb8, 0x5a, 0xd8,
0x2f, 0x44, 0x69, 0x59, 0x90, 0x3c, 0x82, 0xab, 0x72, 0x72, 0x98, 0x4f, 0x92, 0xe7, 0x32, 0x8e,
0x9e, 0x66, 0x8a, 0xe7, 0xf3, 0x75, 0xee, 0x1e, 0x79, 0x08, 0x97, 0xf3, 0x47, 0x57, 0xa1, 0x60,
0x5e, 0xa2, 0x67, 0x37, 0xc8, 0x27, 0x70, 0x25, 0x8a, 0x03, 0x3e, 0x8c, 0xa3, 0x23, 0x71, 0x5c,
0xc8, 0x9b, 0x87, 0xe9, 0xbc, 0xad, 0xa7, 0xcb, 0xbf, 0xe8, 0x6c, 0x7c, 0xfc, 0x24, 0x77, 0xfd,
0x70, 0x09, 0x57, 0x8f, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x26, 0xe4, 0xbd, 0xdb, 0x6c, 0x19,
0x00, 0x00,
}

View File

@ -1,6 +1,7 @@
syntax = "proto3";
import "sync_settings.proto";
import 'application_metadata_message.proto';
option go_package = "./;protobuf";
package protobuf;
@ -279,3 +280,17 @@ message BackedUpProfile {
uint64 display_name_clock = 3;
repeated SyncProfilePicture pictures = 4;
}
message RawMessage {
bytes payload = 1;
ApplicationMetadataMessage.Type messageType = 2;
}
message SyncRawMessage {
repeated RawMessage rawMessages = 1;
// we need these to be able to login
bytes subAccountsJsonBytes = 2;
bytes settingsJsonBytes = 3;
bytes nodeConfigJsonBytes = 4;
}

View File

@ -8,15 +8,10 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/url"
"time"
"github.com/status-im/status-go/signal"
)
var globalCertificate *tls.Certificate = nil
@ -90,7 +85,7 @@ func generateTLSCert() error {
return err
}
cert := GenerateX509Cert(sn, notBefore, notAfter, localhost)
cert := GenerateX509Cert(sn, notBefore, notAfter, Localhost)
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
if err != nil {
return err
@ -115,31 +110,6 @@ func PublicTLSCert() (string, error) {
return globalPem, nil
}
func GenerateCertFromKey(pk *ecdsa.PrivateKey, from time.Time, hostname string) (tls.Certificate, []byte, error) {
cert := GenerateX509Cert(makeSerialNumberFromKey(pk), from, from.Add(time.Hour), hostname)
certPem, keyPem, err := GenerateX509PEMs(cert, pk)
if err != nil {
return tls.Certificate{}, nil, err
}
tlsCert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
return tls.Certificate{}, nil, err
}
block, _ := pem.Decode(certPem)
if block == nil {
return tls.Certificate{}, nil, fmt.Errorf("failed to decode certPem")
}
leaf, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return tls.Certificate{}, nil, err
}
tlsCert.Leaf = leaf
return tlsCert, certPem, nil
}
// ToECDSA takes a []byte of D and uses it to create an ecdsa.PublicKey on the elliptic.P256 curve
// this function is basically a P256 curve version of eth-node/crypto.ToECDSA without all the nice validation
func ToECDSA(d []byte) *ecdsa.PrivateKey {
@ -150,82 +120,3 @@ func ToECDSA(d []byte) *ecdsa.PrivateKey {
k.PublicKey.X, k.PublicKey.Y = k.PublicKey.Curve.ScalarBaseMult(d)
return k
}
// verifyCertPublicKey checks that the ecdsa.PublicKey using in a x509.Certificate matches a known ecdsa.PublicKey
func verifyCertPublicKey(cert *x509.Certificate, publicKey *ecdsa.PublicKey) error {
certKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("unexpected public key type, expected ecdsa.PublicKey")
}
if !certKey.Equal(publicKey) {
return fmt.Errorf("server certificate MUST match the given public key")
}
return nil
}
// verifyCertSig checks that a x509.Certificate's Signature verifies against x509.Certificate's PublicKey
// If the x509.Certificate's PublicKey is not an ecdsa.PublicKey an error will be thrown
func verifyCertSig(cert *x509.Certificate) (bool, error) {
var esig struct {
R, S *big.Int
}
if _, err := asn1.Unmarshal(cert.Signature, &esig); err != nil {
return false, err
}
hash := sha256.New()
hash.Write(cert.RawTBSCertificate)
ecKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return false, fmt.Errorf("certificate public is not an ecdsa.PublicKey")
}
verified := ecdsa.Verify(ecKey, hash.Sum(nil), esig.R, esig.S)
return verified, nil
}
// verifyCert verifies an x509.Certificate against a known ecdsa.PublicKey
// combining the checks of verifyCertPublicKey and verifyCertSig.
// If an x509.Certificate fails to verify an error is also thrown
func verifyCert(cert *x509.Certificate, publicKey *ecdsa.PublicKey) error {
err := verifyCertPublicKey(cert, publicKey)
if err != nil {
return err
}
verified, err := verifyCertSig(cert)
if err != nil {
return err
}
if !verified {
return fmt.Errorf("server certificate signature MUST verify")
}
return nil
}
// getServerCert pings a given tls host, extracts and returns its x509.Certificate
// the function expects there to be only 1 certificate
func getServerCert(URL *url.URL) (*x509.Certificate, error) {
conf := &tls.Config{
InsecureSkipVerify: true, // nolint: gosec // Only skip verify to get the server's TLS cert. DO NOT skip for any other reason.
}
conn, err := tls.Dial("tcp", URL.Host, conf)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventConnectionError, Error: err.Error()})
return nil, err
}
defer conn.Close()
// No error on the dial out then the URL.Host is accessible
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess})
certs := conn.ConnectionState().PeerCertificates
if len(certs) != 1 {
return nil, fmt.Errorf("expected 1 TLS certificate, received '%d'", len(certs))
}
return certs[0], nil
}

View File

@ -44,12 +44,12 @@ func (s *CertsSuite) TestGenerateX509Cert() {
notBefore := time.Now()
notAfter := notBefore.Add(time.Hour)
c1 := GenerateX509Cert(s.SN, notBefore, notAfter, localhost)
s.Require().Exactly([]string{localhost}, c1.DNSNames)
c1 := GenerateX509Cert(s.SN, notBefore, notAfter, Localhost)
s.Require().Exactly([]string{Localhost}, c1.DNSNames)
s.Require().Nil(c1.IPAddresses)
c2 := GenerateX509Cert(s.SN, notBefore, notAfter, defaultIP.String())
c2 := GenerateX509Cert(s.SN, notBefore, notAfter, DefaultIP.String())
s.Require().Len(c2.IPAddresses, 1)
s.Require().Equal(defaultIP.String(), c2.IPAddresses[0].String())
s.Require().Equal(DefaultIP.String(), c2.IPAddresses[0].String())
s.Require().Nil(c2.DNSNames)
}

View File

@ -1,205 +0,0 @@
package server
import (
"bytes"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"github.com/btcsuite/btcutil/base58"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/signal"
)
type PairingClient struct {
*http.Client
PayloadManager
baseAddress *url.URL
certPEM []byte
serverPK *ecdsa.PublicKey
serverMode Mode
serverCert *x509.Certificate
serverChallenge []byte
}
func NewPairingClient(c *ConnectionParams, config *PairingPayloadManagerConfig) (*PairingClient, error) {
u, err := c.URL()
if err != nil {
return nil, err
}
serverCert, err := getServerCert(u)
if err != nil {
return nil, err
}
err = verifyCert(serverCert, c.publicKey)
if err != nil {
return nil, err
}
certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert.Raw})
rootCAs, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
if ok := rootCAs.AppendCertsFromPEM(certPem); !ok {
return nil, fmt.Errorf("failed to append certPem to rootCAs")
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false, // MUST BE FALSE
RootCAs: rootCAs,
},
}
cj, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
pm, err := NewPairingPayloadManager(c.aesKey, config, logutils.ZapLogger().Named("PairingClient"))
if err != nil {
return nil, err
}
return &PairingClient{
Client: &http.Client{Transport: tr, Jar: cj},
baseAddress: u,
certPEM: certPem,
serverCert: serverCert,
serverPK: c.publicKey,
serverMode: c.serverMode,
PayloadManager: pm,
}, nil
}
func (c *PairingClient) PairAccount() error {
switch c.serverMode {
case Receiving:
return c.sendAccountData()
case Sending:
err := c.getChallenge()
if err != nil {
return err
}
return c.receiveAccountData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *PairingClient) sendAccountData() error {
err := c.Mount()
if err != nil {
return err
}
c.baseAddress.Path = pairingReceive
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.PayloadManager.ToSend()))
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
return err
}
if resp.StatusCode != http.StatusOK {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess})
c.PayloadManager.LockPayload()
return nil
}
func (c *PairingClient) receiveAccountData() error {
c.baseAddress.Path = pairingSend
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil {
return err
}
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()})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("status not ok, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
return err
}
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess})
err = c.PayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error()})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess})
return nil
}
func (c *PairingClient) getChallenge() error {
c.baseAddress.Path = pairingChallenge
resp, err := c.Get(c.baseAddress.String())
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
c.serverChallenge, err = ioutil.ReadAll(resp.Body)
return err
}
func StartUpPairingClient(db *multiaccounts.Database, cs, configJSON string) error {
var conf PairingPayloadSourceConfig
err := json.Unmarshal([]byte(configJSON), &conf)
if err != nil {
return err
}
ccp := new(ConnectionParams)
err = ccp.FromString(cs)
if err != nil {
return err
}
c, err := NewPairingClient(ccp, &PairingPayloadManagerConfig{db, conf})
if err != nil {
return err
}
return c.PairAccount()
}

View File

@ -3,11 +3,8 @@ package server
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"encoding/asn1"
"math/big"
"net"
"testing"
"time"
@ -82,42 +79,6 @@ func (tcc *TestCertComponents) SetupCertComponents(t *testing.T) {
tcc.NotAfter = tcc.NotBefore.Add(time.Hour)
}
type TestPairingServerComponents struct {
EphemeralPK *ecdsa.PrivateKey
EphemeralAES []byte
OutboundIP net.IP
Cert tls.Certificate
PS *PairingServer
}
func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing.T) {
var err error
// Get 4 key components for tls.cert generation
// 1) Ephemeral private key
tpsc.EphemeralPK, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
// 2) AES encryption key
tpsc.EphemeralAES, err = makeEncryptionKey(tpsc.EphemeralPK)
require.NoError(t, err)
// 3) Device outbound IP address
tpsc.OutboundIP, err = GetOutboundIP()
require.NoError(t, err)
// Generate tls.Certificate and Server
tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.OutboundIP.String())
require.NoError(t, err)
tpsc.PS, err = NewPairingServer(&Config{
PK: &tpsc.EphemeralPK.PublicKey,
EK: tpsc.EphemeralAES,
Cert: &tpsc.Cert,
Hostname: tpsc.OutboundIP.String()})
require.NoError(t, err)
}
type TestLoggerComponents struct {
Logger *zap.Logger
}
@ -125,33 +86,3 @@ type TestLoggerComponents struct {
func (tlc *TestLoggerComponents) SetupLoggerComponents() {
tlc.Logger = logutils.ZapLogger()
}
type MockEncryptOnlyPayloadManager struct {
*PayloadEncryptionManager
}
func NewMockEncryptOnlyPayloadManager(aesKey []byte) (*MockEncryptOnlyPayloadManager, error) {
pem, err := NewPayloadEncryptionManager(aesKey, logutils.ZapLogger())
if err != nil {
return nil, err
}
return &MockEncryptOnlyPayloadManager{
pem,
}, nil
}
func (m *MockEncryptOnlyPayloadManager) Mount() error {
// Make a random payload
data := make([]byte, 32)
_, err := rand.Read(data)
if err != nil {
return err
}
return m.Encrypt(data)
}
func (m *MockEncryptOnlyPayloadManager) Receive(data []byte) error {
return m.Decrypt(data)
}

View File

@ -1,11 +0,0 @@
package server
import (
"crypto/ecdsa"
"github.com/status-im/status-go/protocol/common"
)
func makeEncryptionKey(key *ecdsa.PrivateKey) ([]byte, error) {
return common.MakeECDHSharedKey(key, &key.PublicKey)
}

View File

@ -2,26 +2,21 @@ package server
import (
"bytes"
"crypto/rand"
"database/sql"
"image"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
"github.com/btcsuite/btcutil/base58"
"go.uber.org/zap"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/identity/colorhash"
"github.com/status-im/status-go/protocol/identity/identicon"
"github.com/status-im/status-go/protocol/identity/ring"
"github.com/status-im/status-go/protocol/images"
"github.com/status-im/status-go/signal"
)
const (
@ -34,15 +29,6 @@ const (
discordAttachmentsPath = basePath + "/discord/attachments"
// Handler routes for pairing
pairingBase = "/pairing"
pairingSend = pairingBase + "/send"
pairingReceive = pairingBase + "/receive"
pairingChallenge = pairingBase + "/challenge"
// Session names
sessionChallenge = "challenge"
sessionBlocked = "blocked"
accountImagesPath = "/accountImages"
contactImagesPath = "/contactImages"
)
@ -398,130 +384,3 @@ func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFun
}
}
}
func handlePairingReceive(ps *PairingServer) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess})
return func(w http.ResponseWriter, r *http.Request) {
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
ps.logger.Error("ioutil.ReadAll(r.Body)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess})
err = ps.PayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error()})
ps.logger.Error("ps.PayloadManager.Receive(payload)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess})
}
}
func handlePairingSend(ps *PairingServer) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess})
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, err := w.Write(ps.PayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error()})
ps.logger.Error("w.Write(ps.PayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess})
ps.PayloadManager.LockPayload()
}
}
func challengeMiddleware(ps *PairingServer, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
ps.logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
blocked, ok := s.Values[sessionBlocked].(bool)
if ok && blocked {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// If the request header doesn't include a challenge don't punish the client, just throw a 403
pc := r.Header.Get(sessionChallenge)
if pc == "" {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
c, err := common.Decrypt(base58.Decode(pc), ps.ek)
if err != nil {
ps.logger.Error("c, err := common.Decrypt(rc, ps.ek)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
// If the challenge is not in the session store don't punish the client, just throw a 403
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// Only if we have both a challenge in the session store and in the request header
// do we entertain blocking the client. Because then we know someone is trying to be sneaky.
if !bytes.Equal(c, challenge) {
s.Values[sessionBlocked] = true
err = s.Save(r, w)
if err != nil {
ps.logger.Error("err = s.Save(r, w)", zap.Error(err))
}
http.Error(w, "forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
}
func handlePairingChallenge(ps *PairingServer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
ps.logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
return
}
var challenge []byte
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
challenge = make([]byte, 64)
_, err = rand.Read(challenge)
if err != nil {
ps.logger.Error("_, err = rand.Read(auth)", zap.Error(err))
return
}
s.Values[sessionChallenge] = challenge
err = s.Save(r, w)
if err != nil {
ps.logger.Error("err = s.Save(r, w)", zap.Error(err))
return
}
}
w.Header().Set("Content-Type", "application/octet-stream")
_, err = w.Write(challenge)
if err != nil {
ps.logger.Error("_, err = w.Write(challenge)", zap.Error(err))
return
}
}
}

View File

@ -5,8 +5,8 @@ import (
)
var (
defaultIP = net.IP{127, 0, 0, 1}
localhost = "localhost"
DefaultIP = net.IP{127, 0, 0, 1}
Localhost = "Localhost"
)
func GetOutboundIP() (net.IP, error) {

View File

@ -1,87 +0,0 @@
package server
import (
"crypto/rand"
"encoding/hex"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
func TestGetOutboundIPSuite(t *testing.T) {
suite.Run(t, new(GetOutboundIPSuite))
}
type GetOutboundIPSuite struct {
suite.Suite
TestPairingServerComponents
}
func (s *GetOutboundIPSuite) SetupSuite() {
s.SetupPairingServerComponents(s.T())
}
func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
say, ok := r.URL.Query()["say"]
if !ok || len(say) == 0 {
say = append(say, "nothing")
}
_, err := w.Write([]byte("Hello I like to be a tls server. You said: `" + say[0] + "` " + time.Now().String()))
if err != nil {
require.NoError(t, err)
}
}
}
func makeThingToSay() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
func (s *GetOutboundIPSuite) TestGetOutboundIPWithFullServerE2e() {
s.PS.mode = Sending
s.PS.SetHandlers(HandlerPatternMap{"/hello": testHandler(s.T())})
err := s.PS.Start()
s.Require().NoError(err)
// Give time for the sever to be ready, hacky I know, I'll iron this out
time.Sleep(100 * time.Millisecond)
// Server generates a QR code connection string
cp, err := s.PS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(ccp, nil)
s.Require().NoError(err)
thing, err := makeThingToSay()
s.Require().NoError(err)
response, err := c.Get(c.baseAddress.String() + "/hello?say=" + thing)
s.Require().NoError(err)
defer response.Body.Close()
content, err := ioutil.ReadAll(response.Body)
s.Require().NoError(err)
s.Require().Equal("Hello I like to be a tls server. You said: `"+thing+"`", string(content[:109]))
}

168
server/pairing/certs.go Normal file
View File

@ -0,0 +1,168 @@
package pairing
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/url"
"time"
"github.com/status-im/status-go/signal"
)
func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
h := sha256.New()
h.Write(append(pk.D.Bytes(), append(pk.Y.Bytes(), pk.X.Bytes()...)...))
return new(big.Int).SetBytes(h.Sum(nil))
}
func GenerateX509Cert(sn *big.Int, from, to time.Time, hostname string) *x509.Certificate {
c := &x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
NotBefore: from,
NotAfter: to,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
ip := net.ParseIP(hostname)
if ip != nil {
c.IPAddresses = []net.IP{ip}
} else {
c.DNSNames = []string{hostname}
}
return c
}
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &key.PublicKey, key)
if err != nil {
return
}
certPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return
}
keyPem = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
return
}
func GenerateCertFromKey(pk *ecdsa.PrivateKey, from time.Time, hostname string) (tls.Certificate, []byte, error) {
cert := GenerateX509Cert(makeSerialNumberFromKey(pk), from, from.Add(time.Hour), hostname)
certPem, keyPem, err := GenerateX509PEMs(cert, pk)
if err != nil {
return tls.Certificate{}, nil, err
}
tlsCert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
return tls.Certificate{}, nil, err
}
block, _ := pem.Decode(certPem)
if block == nil {
return tls.Certificate{}, nil, fmt.Errorf("failed to decode certPem")
}
leaf, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return tls.Certificate{}, nil, err
}
tlsCert.Leaf = leaf
return tlsCert, certPem, nil
}
// verifyCertPublicKey checks that the ecdsa.PublicKey using in a x509.Certificate matches a known ecdsa.PublicKey
func verifyCertPublicKey(cert *x509.Certificate, publicKey *ecdsa.PublicKey) error {
certKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("unexpected public key type, expected ecdsa.PublicKey")
}
if !certKey.Equal(publicKey) {
return fmt.Errorf("server certificate MUST match the given public key")
}
return nil
}
// verifyCertSig checks that a x509.Certificate's Signature verifies against x509.Certificate's PublicKey
// If the x509.Certificate's PublicKey is not an ecdsa.PublicKey an error will be thrown
func verifyCertSig(cert *x509.Certificate) (bool, error) {
var esig struct {
R, S *big.Int
}
if _, err := asn1.Unmarshal(cert.Signature, &esig); err != nil {
return false, err
}
hash := sha256.New()
hash.Write(cert.RawTBSCertificate)
ecKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return false, fmt.Errorf("certificate public is not an ecdsa.PublicKey")
}
verified := ecdsa.Verify(ecKey, hash.Sum(nil), esig.R, esig.S)
return verified, nil
}
// verifyCert verifies an x509.Certificate against a known ecdsa.PublicKey
// combining the checks of verifyCertPublicKey and verifyCertSig.
// If an x509.Certificate fails to verify an error is also thrown
func verifyCert(cert *x509.Certificate, publicKey *ecdsa.PublicKey) error {
err := verifyCertPublicKey(cert, publicKey)
if err != nil {
return err
}
verified, err := verifyCertSig(cert)
if err != nil {
return err
}
if !verified {
return fmt.Errorf("server certificate signature MUST verify")
}
return nil
}
// getServerCert pings a given tls host, extracts and returns its x509.Certificate
// the function expects there to be only 1 certificate
func getServerCert(URL *url.URL) (*x509.Certificate, error) {
conf := &tls.Config{
InsecureSkipVerify: true, // nolint: gosec // Only skip verify to get the server's TLS cert. DO NOT skip for any other reason.
}
conn, err := tls.Dial("tcp", URL.Host, conf)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventConnectionError, Error: err.Error(), Action: ActionConnect})
return nil, err
}
defer conn.Close()
// No error on the dial out then the URL.Host is accessible
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionConnect})
certs := conn.ConnectionState().PeerCertificates
if len(certs) != 1 {
return nil, fmt.Errorf("expected 1 TLS certificate, received '%d'", len(certs))
}
return certs[0], nil
}

300
server/pairing/client.go Normal file
View File

@ -0,0 +1,300 @@
package pairing
import (
"bytes"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"github.com/btcsuite/btcutil/base58"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/signal"
)
type Client struct {
*http.Client
PayloadManager
rawMessagePayloadManager *RawMessagePayloadManager
baseAddress *url.URL
certPEM []byte
serverPK *ecdsa.PublicKey
serverMode Mode
serverCert *x509.Certificate
serverChallenge []byte
}
func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, config *AccountPayloadManagerConfig) (*Client, error) {
u, err := c.URL()
if err != nil {
return nil, err
}
serverCert, err := getServerCert(u)
if err != nil {
return nil, err
}
err = verifyCert(serverCert, c.publicKey)
if err != nil {
return nil, err
}
certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert.Raw})
rootCAs, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
if ok := rootCAs.AppendCertsFromPEM(certPem); !ok {
return nil, fmt.Errorf("failed to append certPem to rootCAs")
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false, // MUST BE FALSE
RootCAs: rootCAs,
},
}
cj, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
pm, err := NewAccountPayloadManager(c.aesKey, config, logutils.ZapLogger().Named("Client"))
if err != nil {
return nil, err
}
rmpm, err := NewRawMessagePayloadManager(logutils.ZapLogger().Named("Client"), pm.accountPayload, c.aesKey, backend, config.KeystorePath)
if err != nil {
return nil, err
}
return &Client{
Client: &http.Client{Transport: tr, Jar: cj},
baseAddress: u,
certPEM: certPem,
serverCert: serverCert,
serverPK: c.publicKey,
serverMode: c.serverMode,
PayloadManager: pm,
rawMessagePayloadManager: rmpm,
}, nil
}
func (c *Client) PairAccount() error {
switch c.serverMode {
case Receiving:
return c.sendAccountData()
case Sending:
err := c.getChallenge()
if err != nil {
return err
}
return c.receiveAccountData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *Client) PairSyncDevice() error {
switch c.serverMode {
case Receiving:
return c.sendSyncDeviceData()
case Sending:
return c.receiveSyncDeviceData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *Client) sendSyncDeviceData() error {
err := c.rawMessagePayloadManager.Mount()
if err != nil {
return err
}
c.baseAddress.Path = pairingSyncDeviceReceive
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.rawMessagePayloadManager.ToSend()))
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
if resp.StatusCode != http.StatusOK {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
return nil
}
func (c *Client) receiveSyncDeviceData() error {
c.baseAddress.Path = pairingSyncDeviceSend
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil {
return err
}
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: ActionSyncDevice})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("status not ok, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
payload, err := io.ReadAll(resp.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
err = c.rawMessagePayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
return nil
}
func (c *Client) sendAccountData() error {
err := c.Mount()
if err != nil {
return err
}
c.baseAddress.Path = pairingReceiveAccount
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.PayloadManager.ToSend()))
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
if resp.StatusCode != http.StatusOK {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
c.PayloadManager.LockPayload()
return nil
}
func (c *Client) receiveAccountData() error {
c.baseAddress.Path = pairingSendAccount
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil {
return err
}
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: ActionPairingAccount})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("status not ok, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
err = c.PayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingAccount})
return nil
}
func (c *Client) getChallenge() error {
c.baseAddress.Path = pairingChallenge
resp, err := c.Get(c.baseAddress.String())
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
c.serverChallenge, err = ioutil.ReadAll(resp.Body)
return err
}
func StartUpPairingClient(backend *api.GethStatusBackend, cs, configJSON string) error {
c, err := setupClient(backend, cs, configJSON)
if err != nil {
return err
}
err = c.PairAccount()
if err != nil {
return err
}
return c.PairSyncDevice()
}
func setupClient(backend *api.GethStatusBackend, cs string, configJSON string) (*Client, error) {
var conf PayloadSourceConfig
err := json.Unmarshal([]byte(configJSON), &conf)
if err != nil {
return nil, err
}
ccp := new(ConnectionParams)
err = ccp.FromString(cs)
if err != nil {
return nil, err
}
c, err := NewPairingClient(backend, ccp, &AccountPayloadManagerConfig{backend.GetMultiaccountDB(), &conf})
if err != nil {
return nil, err
}
return c, nil
}

View File

@ -0,0 +1,165 @@
package pairing
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"encoding/asn1"
"math/big"
"net"
"testing"
"time"
"github.com/btcsuite/btcutil/base58"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/server"
)
const (
X = "7744735542292224619198421067303535767629647588258222392379329927711683109548"
Y = "6855516769916529066379811647277920115118980625614889267697023742462401590771"
D = "38564357061962143106230288374146033267100509055924181407058066820384455255240"
AES = "BbnZ7Gc66t54a9kEFCf7FW8SGQuYypwHVeNkRYeNoqV6"
DB58 = "6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh"
SN = "91849736469742262272885892667727604096707836853856473239722372976236128900962"
CertTime = "eQUriVtGtkWhPJFeLZjF"
)
type TestKeyComponents struct {
X *big.Int
Y *big.Int
D *big.Int
AES []byte
DBytes []byte
PK *ecdsa.PrivateKey
}
func (tk *TestKeyComponents) SetupKeyComponents(t *testing.T) {
var ok bool
tk.X, ok = new(big.Int).SetString(X, 10)
require.True(t, ok)
tk.Y, ok = new(big.Int).SetString(Y, 10)
require.True(t, ok)
tk.D, ok = new(big.Int).SetString(D, 10)
require.True(t, ok)
tk.AES = base58.Decode(AES)
require.Len(t, tk.AES, 32)
tk.DBytes = base58.Decode(DB58)
require.Exactly(t, tk.D.Bytes(), tk.DBytes)
tk.PK = &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P256(),
X: tk.X,
Y: tk.Y,
},
D: tk.D,
}
}
type TestCertComponents struct {
NotBefore, NotAfter time.Time
SN *big.Int
}
func (tcc *TestCertComponents) SetupCertComponents(t *testing.T) {
var ok bool
tcc.SN, ok = new(big.Int).SetString(SN, 10)
require.True(t, ok)
_, err := asn1.Unmarshal(base58.Decode(CertTime), &tcc.NotBefore)
require.NoError(t, err)
tcc.NotAfter = tcc.NotBefore.Add(time.Hour)
}
type TestPairingServerComponents struct {
EphemeralPK *ecdsa.PrivateKey
EphemeralAES []byte
OutboundIP net.IP
Cert tls.Certificate
PS *Server
}
func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing.T) {
var err error
// Get 4 key components for tls.cert generation
// 1) Ephemeral private key
tpsc.EphemeralPK, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
// 2) AES encryption key
tpsc.EphemeralAES, err = common.MakeECDHSharedKey(tpsc.EphemeralPK, &tpsc.EphemeralPK.PublicKey)
require.NoError(t, err)
// 3) Device outbound IP address
tpsc.OutboundIP, err = server.GetOutboundIP()
require.NoError(t, err)
// Generate tls.Certificate and Server
tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.OutboundIP.String())
require.NoError(t, err)
tpsc.PS, err = NewPairingServer(nil, &Config{
PK: &tpsc.EphemeralPK.PublicKey,
EK: tpsc.EphemeralAES,
Cert: &tpsc.Cert,
Hostname: tpsc.OutboundIP.String(),
AccountPayloadManagerConfig: &AccountPayloadManagerConfig{
PayloadSourceConfig: &PayloadSourceConfig{
KeystorePath: "",
},
}})
require.NoError(t, err)
}
type TestLoggerComponents struct {
Logger *zap.Logger
}
func (tlc *TestLoggerComponents) SetupLoggerComponents() {
tlc.Logger = logutils.ZapLogger()
}
type MockEncryptOnlyPayloadManager struct {
*PayloadEncryptionManager
}
func NewMockEncryptOnlyPayloadManager(aesKey []byte) (*MockEncryptOnlyPayloadManager, error) {
pem, err := NewPayloadEncryptionManager(aesKey, logutils.ZapLogger())
if err != nil {
return nil, err
}
return &MockEncryptOnlyPayloadManager{
pem,
}, nil
}
func (m *MockEncryptOnlyPayloadManager) Mount() error {
// Make a random payload
data := make([]byte, 32)
_, err := rand.Read(data)
if err != nil {
return err
}
return m.Encrypt(data)
}
func (m *MockEncryptOnlyPayloadManager) Receive(data []byte) error {
return m.Decrypt(data)
}

View File

@ -1,4 +1,4 @@
package server
package pairing
import (
"crypto/ecdsa"

View File

@ -1,9 +1,11 @@
package server
package pairing
import (
"testing"
"github.com/stretchr/testify/suite"
internalServer "github.com/status-im/status-go/server"
)
var (
@ -20,7 +22,7 @@ type ConnectionParamsSuite struct {
TestCertComponents
TestLoggerComponents
server *PairingServer
server *Server
}
func (s *ConnectionParamsSuite) SetupSuite() {
@ -28,14 +30,14 @@ func (s *ConnectionParamsSuite) SetupSuite() {
s.SetupCertComponents(s.T())
s.SetupLoggerComponents()
cert, _, err := GenerateCertFromKey(s.PK, s.NotBefore, defaultIP.String())
cert, _, err := GenerateCertFromKey(s.PK, s.NotBefore, internalServer.DefaultIP.String())
s.Require().NoError(err)
bs := NewServer(&cert, defaultIP.String(), nil, s.Logger)
bs := internalServer.NewServer(&cert, internalServer.DefaultIP.String(), nil, s.Logger)
err = bs.SetPort(1337)
s.Require().NoError(err)
s.server = &PairingServer{
s.server = &Server{
Server: bs,
pk: &s.PK.PublicKey,
ek: s.AES,
@ -62,7 +64,7 @@ func (s *ConnectionParamsSuite) TestConnectionParams_Generate() {
s.Require().NoError(err)
s.Require().Equal("https://127.0.0.1:1337", u.String())
s.Require().Equal(defaultIP.String(), u.Hostname())
s.Require().Equal(internalServer.DefaultIP.String(), u.Hostname())
s.Require().Equal("1337", u.Port())
s.Require().True(cp.publicKey.Equal(&s.PK.PublicKey))

View File

@ -1,4 +1,4 @@
package server
package pairing
// EventType type for event types.
type EventType string
@ -19,6 +19,15 @@ const (
// Event is a type for transfer events.
type Event struct {
Type EventType `json:"type"`
Error string `json:"error,omitempty"`
Type EventType `json:"type"`
Error string `json:"error,omitempty"`
Action Action `json:"action"`
}
type Action int
const (
ActionPairingAccount = iota + 1
ActionSyncDevice
ActionConnect
)

205
server/pairing/handlers.go Normal file
View File

@ -0,0 +1,205 @@
package pairing
import (
"bytes"
"crypto/rand"
"io"
"net/http"
"github.com/btcsuite/btcutil/base58"
"go.uber.org/zap"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/signal"
)
const (
// Handler routes for pairing
pairingBase = "/pairing"
pairingSendAccount = pairingBase + "/sendAccount"
pairingReceiveAccount = pairingBase + "/receiveAccount"
pairingChallenge = pairingBase + "/challenge"
pairingSyncDeviceSend = pairingBase + "/sendSyncDevice"
pairingSyncDeviceReceive = pairingBase + "/receiveSyncDevice"
// Session names
sessionChallenge = "challenge"
sessionBlocked = "blocked"
)
func handlePairingReceive(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
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: ActionPairingAccount})
logger.Error("ioutil.ReadAll(r.Body)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
err = ps.PayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("ps.PayloadManager.Receive(payload)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingAccount})
}
}
func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
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: ActionSyncDevice})
logger.Error("io.ReadAll(r.Body)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
err = ps.rawMessagePayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("ps.rawMessagePayloadManager.Receive(payload)", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
}
}
func handlePairingSend(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, err := w.Write(ps.PayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("w.Write(ps.PayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
ps.PayloadManager.LockPayload()
}
}
func handlePairingSyncDeviceSend(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
err := ps.rawMessagePayloadManager.Mount()
if err != nil {
// maybe better to use a new event type here instead of EventTransferError?
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("ps.rawMessagePayloadManager.Mount()", zap.Error(err))
return
}
_, err = w.Write(ps.rawMessagePayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("w.Write(ps.rawMessagePayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
ps.rawMessagePayloadManager.LockPayload()
}
}
func challengeMiddleware(ps *Server, next http.Handler) http.HandlerFunc {
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
blocked, ok := s.Values[sessionBlocked].(bool)
if ok && blocked {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// If the request header doesn't include a challenge don't punish the client, just throw a 403
pc := r.Header.Get(sessionChallenge)
if pc == "" {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
c, err := common.Decrypt(base58.Decode(pc), ps.ek)
if err != nil {
logger.Error("c, err := common.Decrypt(rc, ps.ek)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
// If the challenge is not in the session store don't punish the client, just throw a 403
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// Only if we have both a challenge in the session store and in the request header
// do we entertain blocking the client. Because then we know someone is trying to be sneaky.
if !bytes.Equal(c, challenge) {
s.Values[sessionBlocked] = true
err = s.Save(r, w)
if err != nil {
logger.Error("err = s.Save(r, w)", zap.Error(err))
}
http.Error(w, "forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
}
func handlePairingChallenge(ps *Server) http.HandlerFunc {
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
return
}
var challenge []byte
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
challenge = make([]byte, 64)
_, err = rand.Read(challenge)
if err != nil {
logger.Error("_, err = rand.Read(auth)", zap.Error(err))
return
}
s.Values[sessionChallenge] = challenge
err = s.Save(r, w)
if err != nil {
logger.Error("err = s.Save(r, w)", zap.Error(err))
return
}
}
w.Header().Set("Content-Type", "application/octet-stream")
_, err = w.Write(challenge)
if err != nil {
logger.Error("_, err = w.Write(challenge)", zap.Error(err))
return
}
}
}

View File

@ -1,4 +1,4 @@
package server
package pairing
import (
"crypto/rand"
@ -8,6 +8,8 @@ import (
"os"
"path/filepath"
"github.com/status-im/status-go/api"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
@ -42,32 +44,38 @@ type PayloadManager interface {
LockPayload()
}
// PairingPayloadSourceConfig represents location and access data of the pairing payload
// PayloadSourceConfig represents location and access data of the pairing payload
// ONLY available from the application client
type PairingPayloadSourceConfig struct {
type PayloadSourceConfig struct {
// required
KeystorePath string `json:"keystorePath"`
KeyUID string `json:"keyUID"`
Password string `json:"password"`
// following 2 fields r optional.
// optional cases:
// 1. server mode is Receiving and server side doesn't contain this info
// 2. server mode is Sending and client side doesn't contain this info
// they are required in other cases
KeyUID string `json:"keyUID"`
Password string `json:"password"`
}
// PairingPayloadManagerConfig represents the initialisation parameters required for a PairingPayloadManager
type PairingPayloadManagerConfig struct {
// AccountPayloadManagerConfig represents the initialisation parameters required for a AccountPayloadManager
type AccountPayloadManagerConfig struct {
DB *multiaccounts.Database
PairingPayloadSourceConfig
*PayloadSourceConfig
}
// PairingPayloadManager is responsible for the whole lifecycle of a PairingPayload
type PairingPayloadManager struct {
logger *zap.Logger
pp *PairingPayload
// AccountPayloadManager is responsible for the whole lifecycle of a AccountPayload
type AccountPayloadManager struct {
logger *zap.Logger
accountPayload *AccountPayload
*PayloadEncryptionManager
ppm *PairingPayloadMarshaller
ppr PayloadRepository
accountPayloadMarshaller *AccountPayloadMarshaller
payloadRepository PayloadRepository
}
// NewPairingPayloadManager generates a new and initialised PairingPayloadManager
func NewPairingPayloadManager(aesKey []byte, config *PairingPayloadManagerConfig, logger *zap.Logger) (*PairingPayloadManager, error) {
l := logger.Named("PairingPayloadManager")
// NewAccountPayloadManager generates a new and initialised AccountPayloadManager
func NewAccountPayloadManager(aesKey []byte, config *AccountPayloadManagerConfig, logger *zap.Logger) (*AccountPayloadManager, error) {
l := logger.Named("AccountPayloadManager")
l.Debug("fired", zap.Binary("aesKey", aesKey), zap.Any("config", config))
pem, err := NewPayloadEncryptionManager(aesKey, l)
@ -75,74 +83,74 @@ func NewPairingPayloadManager(aesKey []byte, config *PairingPayloadManagerConfig
return nil, err
}
// A new SHARED PairingPayload
p := new(PairingPayload)
// A new SHARED AccountPayload
p := new(AccountPayload)
return &PairingPayloadManager{
return &AccountPayloadManager{
logger: l,
pp: p,
accountPayload: p,
PayloadEncryptionManager: pem,
ppm: NewPairingPayloadMarshaller(p, l),
ppr: NewPairingPayloadRepository(p, config),
accountPayloadMarshaller: NewPairingPayloadMarshaller(p, l),
payloadRepository: NewAccountPayloadRepository(p, config),
}, nil
}
// Mount loads and prepares the payload to be stored in the PairingPayloadManager's state ready for later access
func (ppm *PairingPayloadManager) Mount() error {
l := ppm.logger.Named("Mount()")
// Mount loads and prepares the payload to be stored in the AccountPayloadManager's state ready for later access
func (apm *AccountPayloadManager) Mount() error {
l := apm.logger.Named("Mount()")
l.Debug("fired")
err := ppm.ppr.LoadFromSource()
err := apm.payloadRepository.LoadFromSource()
if err != nil {
return err
}
l.Debug("after LoadFromSource")
pb, err := ppm.ppm.MarshalToProtobuf()
pb, err := apm.accountPayloadMarshaller.MarshalToProtobuf()
if err != nil {
return err
}
l.Debug(
"after MarshalToProtobuf",
zap.Any("ppm.ppm.keys", ppm.ppm.keys),
zap.Any("ppm.ppm.multiaccount", ppm.ppm.multiaccount),
zap.String("ppm.ppm.password", ppm.ppm.password),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apm.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apm.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apm.accountPayloadMarshaller.password),
zap.Binary("pb", pb),
)
return ppm.Encrypt(pb)
return apm.Encrypt(pb)
}
// Receive takes a []byte representing raw data, parses and stores the data
func (ppm *PairingPayloadManager) Receive(data []byte) error {
l := ppm.logger.Named("Receive()")
func (apm *AccountPayloadManager) Receive(data []byte) error {
l := apm.logger.Named("Receive()")
l.Debug("fired")
err := ppm.Decrypt(data)
err := apm.Decrypt(data)
if err != nil {
return err
}
l.Debug("after Decrypt")
err = ppm.ppm.UnmarshalProtobuf(ppm.Received())
err = apm.accountPayloadMarshaller.UnmarshalProtobuf(apm.Received())
if err != nil {
return err
}
l.Debug(
"after UnmarshalProtobuf",
zap.Any("ppm.ppm.keys", ppm.ppm.keys),
zap.Any("ppm.ppm.multiaccount", ppm.ppm.multiaccount),
zap.String("ppm.ppm.password", ppm.ppm.password),
zap.Binary("ppm.Received()", ppm.Received()),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apm.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apm.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apm.accountPayloadMarshaller.password),
zap.Binary("accountPayloadMarshaller.Received()", apm.Received()),
)
return ppm.ppr.StoreToSource()
return apm.payloadRepository.StoreToSource()
}
// ResetPayload resets all payload state managed by the PairingPayloadManager
func (ppm *PairingPayloadManager) ResetPayload() {
ppm.pp.ResetPayload()
ppm.PayloadEncryptionManager.ResetPayload()
// ResetPayload resets all payload state managed by the AccountPayloadManager
func (apm *AccountPayloadManager) ResetPayload() {
apm.accountPayload.ResetPayload()
apm.PayloadEncryptionManager.ResetPayload()
}
// EncryptionPayload represents the plain text and encrypted text of payload data
@ -247,28 +255,28 @@ func (pem *PayloadEncryptionManager) LockPayload() {
pem.received.lock()
}
// PairingPayload represents the payload structure a PairingServer handles
type PairingPayload struct {
// AccountPayload represents the payload structure a Server handles
type AccountPayload struct {
keys map[string][]byte
multiaccount *multiaccounts.Account
password string
}
func (pp *PairingPayload) ResetPayload() {
*pp = PairingPayload{}
func (ap *AccountPayload) ResetPayload() {
*ap = AccountPayload{}
}
// PairingPayloadMarshaller is responsible for marshalling and unmarshalling PairingServer payload data
type PairingPayloadMarshaller struct {
// AccountPayloadMarshaller is responsible for marshalling and unmarshalling Server payload data
type AccountPayloadMarshaller struct {
logger *zap.Logger
*PairingPayload
*AccountPayload
}
func NewPairingPayloadMarshaller(p *PairingPayload, logger *zap.Logger) *PairingPayloadMarshaller {
return &PairingPayloadMarshaller{logger: logger, PairingPayload: p}
func NewPairingPayloadMarshaller(ap *AccountPayload, logger *zap.Logger) *AccountPayloadMarshaller {
return &AccountPayloadMarshaller{logger: logger, AccountPayload: ap}
}
func (ppm *PairingPayloadMarshaller) MarshalToProtobuf() ([]byte, error) {
func (ppm *AccountPayloadMarshaller) MarshalToProtobuf() ([]byte, error) {
return proto.Marshal(&protobuf.LocalPairingPayload{
Keys: ppm.accountKeysToProtobuf(),
Multiaccount: ppm.multiaccount.ToProtobuf(),
@ -276,7 +284,7 @@ func (ppm *PairingPayloadMarshaller) MarshalToProtobuf() ([]byte, error) {
})
}
func (ppm *PairingPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
func (ppm *AccountPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
var keys []*protobuf.LocalPairingPayload_Key
for name, data := range ppm.keys {
keys = append(keys, &protobuf.LocalPairingPayload_Key{Name: name, Data: data})
@ -284,7 +292,7 @@ func (ppm *PairingPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPa
return keys
}
func (ppm *PairingPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
func (ppm *AccountPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
l := ppm.logger.Named("UnmarshalProtobuf()")
l.Debug("fired")
@ -306,7 +314,7 @@ func (ppm *PairingPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
return nil
}
func (ppm *PairingPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.LocalPairingPayload_Key) {
func (ppm *AccountPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.LocalPairingPayload_Key) {
l := ppm.logger.Named("accountKeysFromProtobuf()")
l.Debug("fired")
@ -320,11 +328,11 @@ func (ppm *PairingPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.
l.Debug(
"after for _, key := range pbKeys",
zap.Any("pbKeys", pbKeys),
zap.Any("ppm.keys", ppm.keys),
zap.Any("accountPayloadMarshaller.keys", ppm.keys),
)
}
func (ppm *PairingPayloadMarshaller) multiaccountFromProtobuf(pbMultiAccount *protobuf.MultiAccount) {
func (ppm *AccountPayloadMarshaller) multiaccountFromProtobuf(pbMultiAccount *protobuf.MultiAccount) {
ppm.multiaccount = new(multiaccounts.Account)
ppm.multiaccount.FromProtobuf(pbMultiAccount)
}
@ -334,18 +342,18 @@ type PayloadRepository interface {
StoreToSource() error
}
// PairingPayloadRepository is responsible for loading, parsing, validating and storing PairingServer payload data
type PairingPayloadRepository struct {
*PairingPayload
// AccountPayloadRepository is responsible for loading, parsing, validating and storing Server payload data
type AccountPayloadRepository struct {
*AccountPayload
multiaccountsDB *multiaccounts.Database
keystorePath, keyUID string
}
func NewPairingPayloadRepository(p *PairingPayload, config *PairingPayloadManagerConfig) *PairingPayloadRepository {
ppr := &PairingPayloadRepository{
PairingPayload: p,
func NewAccountPayloadRepository(p *AccountPayload, config *AccountPayloadManagerConfig) *AccountPayloadRepository {
ppr := &AccountPayloadRepository{
AccountPayload: p,
}
if config == nil {
@ -359,18 +367,18 @@ func NewPairingPayloadRepository(p *PairingPayload, config *PairingPayloadManage
return ppr
}
func (ppr *PairingPayloadRepository) LoadFromSource() error {
err := ppr.loadKeys(ppr.keystorePath)
func (apr *AccountPayloadRepository) LoadFromSource() error {
err := apr.loadKeys(apr.keystorePath)
if err != nil {
return err
}
err = ppr.validateKeys(ppr.password)
err = apr.validateKeys(apr.password)
if err != nil {
return err
}
ppr.multiaccount, err = ppr.multiaccountsDB.GetAccount(ppr.keyUID)
apr.multiaccount, err = apr.multiaccountsDB.GetAccount(apr.keyUID)
if err != nil {
return err
}
@ -378,8 +386,8 @@ func (ppr *PairingPayloadRepository) LoadFromSource() error {
return nil
}
func (ppr *PairingPayloadRepository) loadKeys(keyStorePath string) error {
ppr.keys = make(map[string][]byte)
func (apr *AccountPayloadRepository) loadKeys(keyStorePath string) error {
apr.keys = make(map[string][]byte)
fileWalker := func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
@ -404,7 +412,7 @@ func (ppr *PairingPayloadRepository) loadKeys(keyStorePath string) error {
return fmt.Errorf("account key address has invalid length '%s'", accountKey.Address)
}
ppr.keys[fileInfo.Name()] = rawKeyFile
apr.keys[fileInfo.Name()] = rawKeyFile
return nil
}
@ -417,18 +425,18 @@ func (ppr *PairingPayloadRepository) loadKeys(keyStorePath string) error {
return nil
}
func (ppr *PairingPayloadRepository) StoreToSource() error {
err := ppr.validateKeys(ppr.password)
func (apr *AccountPayloadRepository) StoreToSource() error {
err := apr.validateKeys(apr.password)
if err != nil {
return err
}
err = ppr.storeKeys(ppr.keystorePath)
err = apr.storeKeys(apr.keystorePath)
if err != nil {
return err
}
err = ppr.storeMultiAccount()
err = apr.storeMultiAccount()
if err != nil {
return err
}
@ -437,8 +445,8 @@ func (ppr *PairingPayloadRepository) StoreToSource() error {
return nil
}
func (ppr *PairingPayloadRepository) validateKeys(password string) error {
for _, key := range ppr.keys {
func (apr *AccountPayloadRepository) validateKeys(password string) error {
for _, key := range apr.keys {
k, err := keystore.DecryptKey(key, password)
if err != nil {
return err
@ -453,7 +461,7 @@ func (ppr *PairingPayloadRepository) validateKeys(password string) error {
return nil
}
func (ppr *PairingPayloadRepository) storeKeys(keyStorePath string) error {
func (apr *AccountPayloadRepository) storeKeys(keyStorePath string) error {
if keyStorePath == "" {
return fmt.Errorf("keyStorePath can not be empty")
}
@ -463,10 +471,10 @@ func (ppr *PairingPayloadRepository) storeKeys(keyStorePath string) error {
// If lastDir == "keystore" we presume we need to create the rest of the keystore path
// else we presume the provided keystore is valid
if lastDir == "keystore" {
if ppr.multiaccount == nil || ppr.multiaccount.KeyUID == "" {
if apr.multiaccount == nil || apr.multiaccount.KeyUID == "" {
return fmt.Errorf("no known Key UID")
}
keyStorePath = filepath.Join(keyStorePath, ppr.multiaccount.KeyUID)
keyStorePath = filepath.Join(keyStorePath, apr.multiaccount.KeyUID)
err := os.MkdirAll(keyStorePath, 0777)
if err != nil {
@ -474,7 +482,7 @@ func (ppr *PairingPayloadRepository) storeKeys(keyStorePath string) error {
}
}
for name, data := range ppr.keys {
for name, data := range apr.keys {
accountKey := new(keystore.EncryptedKeyJSONV3)
if err := json.Unmarshal(data, &accountKey); err != nil {
return fmt.Errorf("failed to read key file: %s", err)
@ -492,6 +500,87 @@ func (ppr *PairingPayloadRepository) storeKeys(keyStorePath string) error {
return nil
}
func (ppr *PairingPayloadRepository) storeMultiAccount() error {
return ppr.multiaccountsDB.SaveAccount(*ppr.multiaccount)
func (apr *AccountPayloadRepository) storeMultiAccount() error {
return apr.multiaccountsDB.SaveAccount(*apr.multiaccount)
}
type RawMessagePayloadManager struct {
logger *zap.Logger
// reference from AccountPayloadManager#accountPayload
accountPayload *AccountPayload
*PayloadEncryptionManager
payloadRepository *RawMessageRepository
}
func NewRawMessagePayloadManager(logger *zap.Logger, accountPayload *AccountPayload, aesKey []byte, backend *api.GethStatusBackend, keystorePath string) (*RawMessagePayloadManager, error) {
l := logger.Named("RawMessagePayloadManager")
pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil {
return nil, err
}
return &RawMessagePayloadManager{
logger: l,
accountPayload: accountPayload,
PayloadEncryptionManager: pem,
payloadRepository: NewRawMessageRepository(backend, keystorePath, accountPayload),
}, nil
}
func (r *RawMessagePayloadManager) Mount() error {
err := r.payloadRepository.LoadFromSource()
if err != nil {
return err
}
return r.Encrypt(r.payloadRepository.payload)
}
func (r *RawMessagePayloadManager) Receive(data []byte) error {
err := r.Decrypt(data)
if err != nil {
return err
}
r.payloadRepository.payload = r.Received()
return r.payloadRepository.StoreToSource()
}
func (r *RawMessagePayloadManager) ResetPayload() {
r.payloadRepository.payload = make([]byte, 0)
r.PayloadEncryptionManager.ResetPayload()
}
type RawMessageRepository struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
keystorePath string
accountPayload *AccountPayload
}
func NewRawMessageRepository(backend *api.GethStatusBackend, keystorePath string, accountPayload *AccountPayload) *RawMessageRepository {
return &RawMessageRepository{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
keystorePath: keystorePath,
payload: make([]byte, 0),
accountPayload: accountPayload,
}
}
func (r *RawMessageRepository) LoadFromSource() error {
account := r.accountPayload.multiaccount
if account == nil || account.KeyUID == "" {
return fmt.Errorf("no known KeyUID when loading raw messages")
}
payload, err := r.syncRawMessageHandler.PrepareRawMessage(account.KeyUID)
if err != nil {
return err
}
r.payload = payload
return nil
}
func (r *RawMessageRepository) StoreToSource() error {
accountPayload := r.accountPayload
if accountPayload == nil || accountPayload.multiaccount == nil {
return fmt.Errorf("no known multiaccount when storing raw messages")
}
return r.syncRawMessageHandler.HandleRawMessage(accountPayload.multiaccount, accountPayload.password, r.keystorePath, r.payload)
}

View File

@ -1,4 +1,4 @@
package server
package pairing
import (
"bytes"
@ -44,8 +44,8 @@ type PayloadMarshallerSuite struct {
teardown func()
config1 *PairingPayloadManagerConfig
config2 *PairingPayloadManagerConfig
config1 *AccountPayloadManagerConfig
config2 *AccountPayloadManagerConfig
}
func setupTestDB(t *testing.T) (*multiaccounts.Database, func()) {
@ -132,18 +132,18 @@ func (pms *PayloadMarshallerSuite) SetupTest() {
err := db1.SaveAccount(expected)
pms.Require().NoError(err)
pms.config1 = &PairingPayloadManagerConfig{
pms.config1 = &AccountPayloadManagerConfig{
DB: db1,
PairingPayloadSourceConfig: PairingPayloadSourceConfig{
PayloadSourceConfig: &PayloadSourceConfig{
KeystorePath: keystore1,
KeyUID: keyUID,
Password: password,
},
}
pms.config2 = &PairingPayloadManagerConfig{
pms.config2 = &AccountPayloadManagerConfig{
DB: db2,
PairingPayloadSourceConfig: PairingPayloadSourceConfig{
PayloadSourceConfig: &PayloadSourceConfig{
KeystorePath: keystore2,
KeyUID: keyUID,
Password: password,
@ -156,11 +156,11 @@ func (pms *PayloadMarshallerSuite) TearDownTest() {
}
func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_LoadPayloads() {
// Make a PairingPayload
pp := new(PairingPayload)
// Make a Payload
pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1
ppr := NewPairingPayloadRepository(pp, pms.config1)
ppr := NewAccountPayloadRepository(pp, pms.config1)
err := ppr.LoadFromSource()
pms.Require().NoError(err)
@ -189,11 +189,11 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_LoadPayloads() {
}
func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_MarshalToProtobuf() {
// Make a PairingPayload
pp := new(PairingPayload)
// Make a Payload
pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1
ppr := NewPairingPayloadRepository(pp, pms.config1)
ppr := NewAccountPayloadRepository(pp, pms.config1)
err := ppr.LoadFromSource()
pms.Require().NoError(err)
@ -218,11 +218,11 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_MarshalToProtobuf() {
}
func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_UnmarshalProtobuf() {
// Make a PairingPayload
pp := new(PairingPayload)
// Make a Payload
pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1
ppr := NewPairingPayloadRepository(pp, pms.config1)
ppr := NewAccountPayloadRepository(pp, pms.config1)
err := ppr.LoadFromSource()
pms.Require().NoError(err)
@ -232,8 +232,8 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_UnmarshalProtobuf() {
pb, err := ppm.MarshalToProtobuf()
pms.Require().NoError(err)
// Make a PairingPayload
pp2 := new(PairingPayload)
// Make a Payload
pp2 := new(AccountPayload)
// Make PairingPayloadMarshaller 2
ppm2 := NewPairingPayloadMarshaller(pp2, pms.Logger)
@ -271,11 +271,11 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_UnmarshalProtobuf() {
}
func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
// Make a PairingPayload
pp := new(PairingPayload)
// Make a Payload
pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1
ppr := NewPairingPayloadRepository(pp, pms.config1)
ppr := NewAccountPayloadRepository(pp, pms.config1)
err := ppr.LoadFromSource()
pms.Require().NoError(err)
@ -285,8 +285,8 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
pb, err := ppm.MarshalToProtobuf()
pms.Require().NoError(err)
// Make a PairingPayload
pp2 := new(PairingPayload)
// Make a Payload
pp2 := new(AccountPayload)
// Make PairingPayloadMarshaller 2
ppm2 := NewPairingPayloadMarshaller(pp2, pms.Logger)
@ -295,7 +295,7 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
pms.Require().NoError(err)
// Make and Load PairingPayloadRepository 2
ppr2 := NewPairingPayloadRepository(pp2, pms.config2)
ppr2 := NewAccountPayloadRepository(pp2, pms.config2)
err = ppr2.StoreToSource()
pms.Require().NoError(err)

View File

@ -0,0 +1,20 @@
package pairing
import (
"context"
"github.com/status-im/status-go/protocol/common"
)
type RawMessageCollector struct {
rawMessages []*common.RawMessage
}
func (r *RawMessageCollector) dispatchMessage(_ context.Context, rawMessage common.RawMessage) (common.RawMessage, error) {
r.rawMessages = append(r.rawMessages, &rawMessage)
return rawMessage, nil
}
func (r *RawMessageCollector) getRawMessages() []*common.RawMessage {
return r.rawMessages
}

View File

@ -0,0 +1,142 @@
package pairing
import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"github.com/google/uuid"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/golang/protobuf/proto"
"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/params"
"github.com/status-im/status-go/protocol/protobuf"
)
type SyncRawMessageHandler struct {
backend *api.GethStatusBackend
}
func NewSyncRawMessageHandler(backend *api.GethStatusBackend) *SyncRawMessageHandler {
return &SyncRawMessageHandler{backend: backend}
}
func (s *SyncRawMessageHandler) PrepareRawMessage(keyUID string) ([]byte, error) {
messenger := s.backend.Messenger()
if messenger == nil {
return nil, fmt.Errorf("messenger is nil when handlePairingSyncDeviceSend")
}
currentAccount, err := s.backend.GetActiveAccount()
if err != nil {
return nil, err
}
if keyUID != currentAccount.KeyUID {
return nil, fmt.Errorf("keyUID not equal")
}
messenger.SetLocalPairing(true)
defer func() {
messenger.SetLocalPairing(false)
}()
rawMessageCollector := new(RawMessageCollector)
err = messenger.SyncDevices(context.TODO(), currentAccount.Name, currentAccount.Identicon, rawMessageCollector.dispatchMessage)
if err != nil {
return nil, err
}
syncRawMessage := new(protobuf.SyncRawMessage)
for _, m := range rawMessageCollector.getRawMessages() {
rawMessage := new(protobuf.RawMessage)
rawMessage.Payload = m.Payload
rawMessage.MessageType = m.MessageType
syncRawMessage.RawMessages = append(syncRawMessage.RawMessages, rawMessage)
}
accountService := s.backend.StatusNode().AccountService()
var (
subAccounts []*accounts.Account
setting settings.Settings
)
subAccounts, err = accountService.GetAccountsByKeyUID(keyUID)
if err != nil {
return nil, err
}
syncRawMessage.SubAccountsJsonBytes, err = json.Marshal(subAccounts)
if err != nil {
return nil, err
}
setting, err = accountService.GetSettings()
if err != nil {
return nil, err
}
syncRawMessage.SettingsJsonBytes, err = json.Marshal(setting)
if err != nil {
return nil, err
}
nodeConfig := s.backend.StatusNode().Config()
if syncRawMessage.NodeConfigJsonBytes, err = json.Marshal(nodeConfig); err != nil {
return nil, err
}
return proto.Marshal(syncRawMessage)
}
func (s *SyncRawMessageHandler) HandleRawMessage(account *multiaccounts.Account, password, keystorePath string, payload []byte) error {
rawMessages, subAccounts, setting, nodeConfig, err := s.unmarshalSyncRawMessage(payload)
if err != nil {
return err
}
newKeystoreDir := filepath.Join(keystorePath, account.KeyUID)
accountManager := s.backend.AccountManager()
err = accountManager.InitKeystore(newKeystoreDir)
if err != nil {
return err
}
nodeConfig.RootDataDir = filepath.Dir(keystorePath)
nodeConfig.DataDir = filepath.Join(nodeConfig.RootDataDir, filepath.Base(nodeConfig.DataDir))
nodeConfig.KeyStoreDir = newKeystoreDir
installationID := uuid.New().String()
nodeConfig.ShhextConfig.InstallationID = installationID
setting.InstallationID = installationID
err = s.backend.StartNodeWithAccountAndInitialConfig(*account, password, *setting, nodeConfig, subAccounts)
if err != nil {
return err
}
messenger := s.backend.Messenger()
return messenger.HandleSyncRawMessages(rawMessages)
}
func (s *SyncRawMessageHandler) unmarshalSyncRawMessage(payload []byte) ([]*protobuf.RawMessage, []*accounts.Account, *settings.Settings, *params.NodeConfig, error) {
var (
syncRawMessage protobuf.SyncRawMessage
subAccounts []*accounts.Account
setting *settings.Settings
nodeConfig *params.NodeConfig
)
err := proto.Unmarshal(payload, &syncRawMessage)
if err != nil {
return nil, nil, nil, nil, err
}
err = json.Unmarshal(syncRawMessage.SubAccountsJsonBytes, &subAccounts)
if err != nil {
return nil, nil, nil, nil, err
}
err = json.Unmarshal(syncRawMessage.SettingsJsonBytes, &setting)
if err != nil {
return nil, nil, nil, nil, err
}
err = json.Unmarshal(syncRawMessage.NodeConfigJsonBytes, &nodeConfig)
if err != nil {
return nil, nil, nil, nil, err
}
return syncRawMessage.RawMessages, subAccounts, setting, nodeConfig, nil
}

View File

@ -1,4 +1,4 @@
package server
package pairing
import (
"crypto/ecdsa"
@ -10,15 +10,18 @@ import (
"net"
"time"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/server"
"github.com/gorilla/sessions"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/multiaccounts"
)
type PairingServer struct {
Server
type Server struct {
server.Server
PayloadManager
rawMessagePayloadManager *RawMessagePayloadManager
pk *ecdsa.PublicKey
ek []byte
@ -35,8 +38,8 @@ type Config struct {
Hostname string
Mode Mode
// Payload management fields
*PairingPayloadManagerConfig
// AccountPayload management fields
*AccountPayloadManagerConfig
}
func makeCookieStore() (*sessions.CookieStore, error) {
@ -55,10 +58,10 @@ func makeCookieStore() (*sessions.CookieStore, error) {
return sessions.NewCookieStore(auth, enc), nil
}
// NewPairingServer returns a *PairingServer init from the given *Config
func NewPairingServer(config *Config) (*PairingServer, error) {
logger := logutils.ZapLogger().Named("PairingServer")
pm, err := NewPairingPayloadManager(config.EK, config.PairingPayloadManagerConfig, logger)
// NewPairingServer returns a *Server init from the given *Config
func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server, error) {
logger := logutils.ZapLogger().Named("Server")
pm, err := NewAccountPayloadManager(config.EK, config.AccountPayloadManagerConfig, logger)
if err != nil {
return nil, err
}
@ -68,25 +71,32 @@ func NewPairingServer(config *Config) (*PairingServer, error) {
return nil, err
}
return &PairingServer{Server: NewServer(
rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, config.EK, backend, config.KeystorePath)
if err != nil {
return nil, err
}
return &Server{Server: server.NewServer(
config.Cert,
config.Hostname,
nil,
logger,
),
pk: config.PK,
ek: config.EK,
mode: config.Mode,
PayloadManager: pm,
cookieStore: cs,
pk: config.PK,
ek: config.EK,
mode: config.Mode,
PayloadManager: pm,
cookieStore: cs,
rawMessagePayloadManager: rmpm,
}, nil
}
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
func (s *PairingServer) MakeConnectionParams() (*ConnectionParams, error) {
netIP := net.ParseIP(s.hostname)
func (s *Server) MakeConnectionParams() (*ConnectionParams, error) {
hostname := s.GetHostname()
netIP := net.ParseIP(hostname)
if netIP == nil {
return nil, fmt.Errorf("invalid ip address given '%s'", s.hostname)
return nil, fmt.Errorf("invalid ip address given '%s'", hostname)
}
netIP4 := netIP.To4()
@ -97,40 +107,42 @@ func (s *PairingServer) MakeConnectionParams() (*ConnectionParams, error) {
return NewConnectionParams(netIP, s.MustGetPort(), s.pk, s.ek, s.mode), nil
}
func (s *PairingServer) StartPairing() error {
func (s *Server) StartPairing() error {
switch s.mode {
case Receiving:
return s.startReceivingAccountData()
return s.startReceivingData()
case Sending:
return s.startSendingAccountData()
return s.startSendingData()
default:
return fmt.Errorf("invalid server mode '%d'", s.mode)
}
}
func (s *PairingServer) startReceivingAccountData() error {
s.SetHandlers(HandlerPatternMap{
pairingReceive: handlePairingReceive(s),
pairingChallenge: handlePairingChallenge(s),
func (s *Server) startReceivingData() error {
s.SetHandlers(server.HandlerPatternMap{
pairingReceiveAccount: handlePairingReceive(s),
pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceReceive: handleParingSyncDeviceReceive(s),
})
return s.Start()
}
func (s *PairingServer) startSendingAccountData() error {
func (s *Server) startSendingData() error {
err := s.Mount()
if err != nil {
return err
}
s.SetHandlers(HandlerPatternMap{
pairingSend: challengeMiddleware(s, handlePairingSend(s)),
pairingChallenge: handlePairingChallenge(s),
s.SetHandlers(server.HandlerPatternMap{
pairingSendAccount: challengeMiddleware(s, handlePairingSend(s)),
pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceSend: challengeMiddleware(s, handlePairingSyncDeviceSend(s)),
})
return s.Start()
}
// MakeFullPairingServer generates a fully configured and randomly seeded PairingServer
func MakeFullPairingServer(db *multiaccounts.Database, mode Mode, storeConfig PairingPayloadSourceConfig) (*PairingServer, error) {
// MakeFullPairingServer generates a fully configured and randomly seeded Server
func MakeFullPairingServer(backend *api.GethStatusBackend, mode Mode, storeConfig *PayloadSourceConfig) (*Server, error) {
tlsKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
@ -142,7 +154,7 @@ func MakeFullPairingServer(db *multiaccounts.Database, mode Mode, storeConfig Pa
return nil, err
}
outboundIP, err := GetOutboundIP()
outboundIP, err := server.GetOutboundIP()
if err != nil {
return nil, err
}
@ -151,8 +163,7 @@ func MakeFullPairingServer(db *multiaccounts.Database, mode Mode, storeConfig Pa
if err != nil {
return nil, err
}
return NewPairingServer(&Config{
return NewPairingServer(backend, &Config{
// Things that can be generated, and CANNOT come from the app client (well they could be this is better)
PK: &tlsKey.PublicKey,
EK: AESKey,
@ -162,26 +173,26 @@ func MakeFullPairingServer(db *multiaccounts.Database, mode Mode, storeConfig Pa
// Things that can't be generated, but DO come from the app client
Mode: mode,
PairingPayloadManagerConfig: &PairingPayloadManagerConfig{
AccountPayloadManagerConfig: &AccountPayloadManagerConfig{
// Things that can't be generated, but DO NOT come from app client
DB: db,
DB: backend.GetMultiaccountDB(),
// Things that can't be generated, but DO come from the app client
PairingPayloadSourceConfig: storeConfig,
PayloadSourceConfig: storeConfig,
},
})
}
// StartUpPairingServer generates a PairingServer, starts the pairing server in the correct mode
// and returns the ConnectionParams string to allow a PairingClient to make a successful connection.
func StartUpPairingServer(db *multiaccounts.Database, mode Mode, configJSON string) (string, error) {
var conf PairingPayloadSourceConfig
// StartUpPairingServer generates a Server, starts the pairing server in the correct mode
// and returns the ConnectionParams string to allow a Client to make a successful connection.
func StartUpPairingServer(backend *api.GethStatusBackend, mode Mode, configJSON string) (string, error) {
var conf PayloadSourceConfig
err := json.Unmarshal([]byte(configJSON), &conf)
if err != nil {
return "", err
}
ps, err := MakeFullPairingServer(db, mode, conf)
ps, err := MakeFullPairingServer(backend, mode, &conf)
if err != nil {
return "", err
}

View File

@ -1,12 +1,19 @@
package server
package pairing
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"io/ioutil"
"net/http"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/server"
)
func TestPairingServerSuite(t *testing.T) {
@ -61,12 +68,14 @@ func (s *PairingServerSuite) TestPairingServer_StartPairing() {
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(ccp, nil)
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
})
s.Require().NoError(err)
// Compare cert values
cert := c.serverCert
cl := s.PS.cert.Leaf
cl := s.PS.GetCert().Leaf
s.Require().Equal(cl.Signature, cert.Signature)
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X))
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y))
@ -102,7 +111,7 @@ func (s *PairingServerSuite) TestPairingServer_StartPairing() {
}
}
func (s *PairingServerSuite) sendingSetup() *PairingClient {
func (s *PairingServerSuite) sendingSetup() *Client {
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
@ -122,7 +131,9 @@ func (s *PairingServerSuite) sendingSetup() *PairingClient {
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(ccp, nil)
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
})
s.Require().NoError(err)
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
@ -187,3 +198,66 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_
s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
}
func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
say, ok := r.URL.Query()["say"]
if !ok || len(say) == 0 {
say = append(say, "nothing")
}
_, err := w.Write([]byte("Hello I like to be a tls server. You said: `" + say[0] + "` " + time.Now().String()))
if err != nil {
require.NoError(t, err)
}
}
}
func makeThingToSay() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() {
s.PS.mode = Sending
s.PS.SetHandlers(server.HandlerPatternMap{"/hello": testHandler(s.T())})
err := s.PS.Start()
s.Require().NoError(err)
// Give time for the sever to be ready, hacky I know, I'll iron this out
time.Sleep(100 * time.Millisecond)
// Server generates a QR code connection string
cp, err := s.PS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
})
s.Require().NoError(err)
thing, err := makeThingToSay()
s.Require().NoError(err)
response, err := c.Get(c.baseAddress.String() + "/hello?say=" + thing)
s.Require().NoError(err)
defer response.Body.Close()
content, err := ioutil.ReadAll(response.Body)
s.Require().NoError(err)
s.Require().Equal("Hello I like to be a tls server. You said: `"+thing+"`", string(content[:109]))
}

View File

@ -0,0 +1,316 @@
package pairing
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/identity/alias"
"github.com/status-im/status-go/services/browsers"
"github.com/status-im/status-go/sqlite"
)
const pathWalletRoot = "m/44'/60'/0'/0"
const pathEIP1581 = "m/43'/60'/1581'"
const pathDefaultChat = pathEIP1581 + "/0'/0"
const pathDefaultWallet = pathWalletRoot + "/0"
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
const keystoreDir = "keystore"
func TestSyncDeviceSuite(t *testing.T) {
suite.Run(t, new(SyncDeviceSuite))
}
type SyncDeviceSuite struct {
suite.Suite
password string
clientAsSenderTmpdir string
clientAsReceiverTmpdir string
}
func (s *SyncDeviceSuite) SetupTest() {
s.password = "password"
clientAsSenderTmpdir, err := os.MkdirTemp("", "TestPairingSyncDeviceClientAsSender")
require.NoError(s.T(), err)
s.clientAsSenderTmpdir = clientAsSenderTmpdir
clientAsReceiverTmpdir, err := os.MkdirTemp("", "TestPairingSyncDeviceClientAsReceiver")
require.NoError(s.T(), err)
s.clientAsReceiverTmpdir = clientAsReceiverTmpdir
}
func (s *SyncDeviceSuite) TearDownTest() {
os.RemoveAll(s.clientAsSenderTmpdir)
os.RemoveAll(s.clientAsReceiverTmpdir)
}
func (s *SyncDeviceSuite) prepareBackendWithAccount(tmpdir string) *api.GethStatusBackend {
backend := s.prepareBackendWithoutAccount(tmpdir)
accountManager := backend.AccountManager()
generator := accountManager.AccountsGenerator()
generatedAccountInfos, err := generator.GenerateAndDeriveAddresses(12, 1, "", paths)
require.NoError(s.T(), err)
generatedAccountInfo := generatedAccountInfos[0]
account := multiaccounts.Account{
KeyUID: generatedAccountInfo.KeyUID,
KDFIterations: sqlite.ReducedKDFIterationsNumber,
}
err = accountManager.InitKeystore(filepath.Join(tmpdir, keystoreDir, account.KeyUID))
require.NoError(s.T(), err)
err = backend.OpenAccounts()
require.NoError(s.T(), err)
derivedAddresses := generatedAccountInfo.Derived
_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, s.password, paths)
require.NoError(s.T(), err)
settings, err := defaultSettings(generatedAccountInfo.GeneratedAccountInfo, derivedAddresses, nil)
require.NoError(s.T(), err)
nodeConfig, err := defaultNodeConfig(tmpdir, settings.InstallationID, account.KeyUID)
require.NoError(s.T(), err)
walletDerivedAccount := derivedAddresses[pathDefaultWallet]
walletAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
KeyUID: generatedAccountInfo.KeyUID,
Address: types.HexToAddress(walletDerivedAccount.Address),
Color: "",
Wallet: true,
Path: pathDefaultWallet,
Name: "Ethereum account",
}
chatDerivedAccount := derivedAddresses[pathDefaultChat]
chatAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
KeyUID: generatedAccountInfo.KeyUID,
Address: types.HexToAddress(chatDerivedAccount.Address),
Name: settings.Name,
Chat: true,
Path: pathDefaultChat,
}
accounts := []*accounts.Account{walletAccount, chatAccount}
err = backend.StartNodeWithAccountAndInitialConfig(account, s.password, *settings, nodeConfig, accounts)
require.NoError(s.T(), err)
return backend
}
func (s *SyncDeviceSuite) prepareBackendWithoutAccount(tmpdir string) *api.GethStatusBackend {
backend := api.NewGethStatusBackend()
backend.UpdateRootDataDir(tmpdir)
return backend
}
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
clientTmpDir := filepath.Join(s.clientAsSenderTmpdir, "client")
clientBackend := s.prepareBackendWithAccount(clientTmpDir)
serverTmpDir := filepath.Join(s.clientAsSenderTmpdir, "server")
serverBackend := s.prepareBackendWithoutAccount(serverTmpDir)
defer func() {
require.NoError(s.T(), serverBackend.Logout())
}()
err := serverBackend.AccountManager().InitKeystore(filepath.Join(serverTmpDir, keystoreDir))
require.NoError(s.T(), err)
err = serverBackend.OpenAccounts()
require.NoError(s.T(), err)
serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir)
configJSON := fmt.Sprintf(`{"KeystorePath":"%s"}`, serverKeystorePath)
cs, err := StartUpPairingServer(serverBackend, Receiving, configJSON)
require.NoError(s.T(), err)
// generate some data for the client
clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
_, err = clientBrowserAPI.StoreBookmark(context.TODO(), browsers.Bookmark{
Name: "status.im",
URL: "https://status.im",
})
require.NoError(s.T(), err)
activeAccount, err := clientBackend.GetActiveAccount()
require.NoError(s.T(), err)
clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, activeAccount.KeyUID)
var config = PayloadSourceConfig{
KeystorePath: clientKeystorePath,
KeyUID: activeAccount.KeyUID,
Password: s.password,
}
configBytes, err := json.Marshal(config)
require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(configBytes))
require.NoError(s.T(), err)
require.NoError(s.T(), clientBackend.Logout())
serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
bookmarks, err := serverBrowserAPI.GetBookmarks(context.TODO())
require.NoError(s.T(), err)
require.Equal(s.T(), 1, len(bookmarks))
require.Equal(s.T(), "status.im", bookmarks[0].Name)
}
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
clientTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "client")
clientBackend := s.prepareBackendWithoutAccount(clientTmpDir)
serverTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "server")
serverBackend := s.prepareBackendWithAccount(serverTmpDir)
defer func() {
require.NoError(s.T(), clientBackend.Logout())
}()
activeAccount, err := serverBackend.GetActiveAccount()
require.NoError(s.T(), err)
serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, activeAccount.KeyUID)
var config = PayloadSourceConfig{
KeystorePath: serverKeystorePath,
KeyUID: activeAccount.KeyUID,
Password: s.password,
}
configBytes, err := json.Marshal(config)
require.NoError(s.T(), err)
cs, err := StartUpPairingServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err)
// generate some data for the server
serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
_, err = serverBrowserAPI.StoreBookmark(context.TODO(), browsers.Bookmark{
Name: "status.im",
URL: "https://status.im",
})
require.NoError(s.T(), err)
err = clientBackend.AccountManager().InitKeystore(filepath.Join(clientTmpDir, keystoreDir))
require.NoError(s.T(), err)
err = clientBackend.OpenAccounts()
require.NoError(s.T(), err)
clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir)
configJSON := fmt.Sprintf(`{"KeystorePath":"%s"}`, clientKeystorePath)
err = StartUpPairingClient(clientBackend, cs, configJSON)
require.NoError(s.T(), err)
require.NoError(s.T(), serverBackend.Logout())
clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
bookmarks, err := clientBrowserAPI.GetBookmarks(context.TODO())
require.NoError(s.T(), err)
require.Equal(s.T(), 1, len(bookmarks))
require.Equal(s.T(), "status.im", bookmarks[0].Name)
}
func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) {
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
settings := &settings.Settings{}
settings.KeyUID = generatedAccountInfo.KeyUID
settings.Address = types.HexToAddress(generatedAccountInfo.Address)
settings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
// Set chat key & name
name, err := alias.GenerateFromPublicKeyString(chatKeyString)
if err != nil {
return nil, err
}
settings.Name = name
settings.PublicKey = chatKeyString
settings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
settings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
settings.Mnemonic = mnemonic
settings.SigningPhrase = "balabala"
settings.SendPushNotifications = true
settings.InstallationID = uuid.New().String()
settings.UseMailservers = true
settings.PreviewPrivacy = true
settings.Currency = "usd"
settings.ProfilePicturesVisibility = 1
settings.LinkPreviewRequestEnabled = true
visibleTokens := make(map[string][]string)
visibleTokens["mainnet"] = []string{"SNT"}
visibleTokensJSON, err := json.Marshal(visibleTokens)
if err != nil {
return nil, err
}
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
settings.WalletVisibleTokens = &visibleTokenJSONRaw
networks := make([]map[string]string, 0)
networksJSON, err := json.Marshal(networks)
if err != nil {
return nil, err
}
networkRawMessage := json.RawMessage(networksJSON)
settings.Networks = &networkRawMessage
settings.CurrentNetwork = "mainnet_rpc"
return settings, nil
}
func defaultNodeConfig(tmpdir, installationID, keyUID string) (*params.NodeConfig, error) {
// Set mainnet
nodeConfig := &params.NodeConfig{}
nodeConfig.NetworkID = 1
nodeConfig.LogLevel = "ERROR"
nodeConfig.DataDir = filepath.Join(tmpdir, "ethereum/mainnet_rpc")
nodeConfig.KeyStoreDir = filepath.Join(tmpdir, keystoreDir, keyUID)
nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
Enabled: true,
URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
}
nodeConfig.Name = "StatusIM"
clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod")
if err != nil {
return nil, err
}
nodeConfig.ClusterConfig = *clusterConfig
nodeConfig.WalletConfig = params.WalletConfig{Enabled: false}
nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: false}
nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
nodeConfig.EnableNTPSync = true
nodeConfig.WakuConfig = params.WakuConfig{
Enabled: true,
LightClient: true,
MinimumPoW: 0.000001,
}
nodeConfig.ShhextConfig = params.ShhextConfig{
BackupDisabledDataDir: "",
InstallationID: installationID,
MaxMessageDeliveryAttempts: 6,
MailServerConfirmations: true,
VerifyTransactionURL: "",
VerifyENSURL: "",
VerifyENSContractAddress: "",
VerifyTransactionChainID: 1,
DataSyncEnabled: true,
PFSEnabled: true,
}
return nodeConfig, nil
}

View File

@ -34,6 +34,18 @@ func (s *Server) getHost() string {
return fmt.Sprintf("%s:%d", s.hostname, s.GetPort())
}
func (s *Server) GetHostname() string {
return s.hostname
}
func (s *Server) GetCert() *tls.Certificate {
return s.cert
}
func (s *Server) GetLogger() *zap.Logger {
return s.logger
}
func (s *Server) mustGetHost() string {
return fmt.Sprintf("%s:%d", s.hostname, s.MustGetPort())
}

View File

@ -28,7 +28,7 @@ func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *mu
s := &MediaServer{
Server: NewServer(
globalCertificate,
localhost,
Localhost,
signal.SendMediaServerStarted,
logutils.ZapLogger().Named("MediaServer"),
),

View File

@ -30,14 +30,14 @@ func (s *ServerURLSuite) SetupTest() {
s.SetupLoggerComponents()
s.server = &MediaServer{Server: Server{
hostname: defaultIP.String(),
hostname: DefaultIP.String(),
portManger: newPortManager(s.Logger, nil),
}}
err := s.server.SetPort(1337)
s.Require().NoError(err)
s.serverNoPort = &MediaServer{Server: Server{
hostname: defaultIP.String(),
hostname: DefaultIP.String(),
portManger: newPortManager(s.Logger, nil),
}}
go func() {

View File

@ -4,6 +4,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/account"
@ -68,3 +69,12 @@ func (s *Service) APIs() []rpc.API {
func (s *Service) Protocols() []p2p.Protocol {
return nil
}
func (s *Service) GetAccountsByKeyUID(keyUID string) ([]*accounts.Account, error) {
return s.db.GetAccountsByKeyUID(keyUID)
}
func (s *Service) GetSettings() (settings.Settings, error) {
return s.db.GetSettings()
}

View File

@ -872,7 +872,7 @@ func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.Messe
}
func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error {
return api.service.messenger.SyncDevices(ctx, name, picture)
return api.service.messenger.SyncDevices(ctx, name, picture, nil)
}
func (api *PublicAPI) AddBookmark(ctx context.Context, bookmark browsers.Bookmark) error {