status-im/status-go

View on GitHub
server/pairing/payload_mounter.go

Summary

Maintainability
A
0 mins
Test Coverage
B
85%
package pairing

import (
    "fmt"
    "strings"

    "go.uber.org/zap"

    "github.com/status-im/status-go/api"
    "github.com/status-im/status-go/multiaccounts"
    "github.com/status-im/status-go/multiaccounts/accounts"
)

type PayloadMounter interface {
    PayloadLocker

    // Mount Loads the payload into the PayloadManager's state
    Mount() error

    // ToSend returns an outbound safe (encrypted) payload
    ToSend() []byte
}

type PayloadLoader interface {
    Load() error
}

type BasePayloadMounter struct {
    *PayloadLockPayload
    *PayloadToSend

    loader     PayloadLoader
    marshaller ProtobufMarshaller
    encryptor  *PayloadEncryptor
}

func NewBasePayloadMounter(loader PayloadLoader, marshaller ProtobufMarshaller, e *PayloadEncryptor) *BasePayloadMounter {
    return &BasePayloadMounter{
        PayloadLockPayload: &PayloadLockPayload{e},
        PayloadToSend:      &PayloadToSend{e},
        loader:             loader,
        marshaller:         marshaller,
        encryptor:          e,
    }
}

// Mount loads and prepares the payload to be stored in the PayloadLoader's state ready for later access
func (bpm *BasePayloadMounter) Mount() error {
    err := bpm.loader.Load()
    if err != nil {
        return err
    }

    p, err := bpm.marshaller.MarshalProtobuf()
    if err != nil {
        return err
    }

    return bpm.encryptor.encrypt(p)
}

/*
|--------------------------------------------------------------------------
| AccountPayload
|--------------------------------------------------------------------------
|
| AccountPayloadMounter, AccountPayloadLoader and AccountPayloadMarshaller
|
*/

// NewAccountPayloadMounter generates a new and initialised AccountPayload flavoured BasePayloadMounter
// responsible for the whole lifecycle of an AccountPayload
func NewAccountPayloadMounter(pe *PayloadEncryptor, config *SenderConfig, logger *zap.Logger) (*BasePayloadMounter, error) {
    l := logger.Named("AccountPayloadLoader")
    l.Debug("fired", zap.Any("config", config))

    pe = pe.Renew()

    // A new SHARED AccountPayload
    p := new(AccountPayload)
    apl, err := NewAccountPayloadLoader(p, config)
    if err != nil {
        return nil, err
    }

    return NewBasePayloadMounter(
        apl,
        NewPairingPayloadMarshaller(p, l),
        pe,
    ), nil
}

// AccountPayloadLoader is responsible for loading, parsing and validating AccountPayload data
type AccountPayloadLoader struct {
    *AccountPayload

    multiaccountsDB *multiaccounts.Database
    keystorePath    string
    keyUID          string
}

func NewAccountPayloadLoader(p *AccountPayload, config *SenderConfig) (*AccountPayloadLoader, error) {
    ppr := &AccountPayloadLoader{
        AccountPayload: p,
    }

    if config == nil {
        return ppr, nil
    }

    ppr.multiaccountsDB = config.DB
    ppr.keyUID = config.KeyUID
    ppr.password = config.Password
    ppr.chatKey = config.ChatKey
    ppr.keystorePath = config.KeystorePath
    return ppr, nil
}

func (apl *AccountPayloadLoader) Load() error {
    apl.keys = make(map[string][]byte)
    err := loadKeys(apl.keys, apl.keystorePath)
    if err != nil {
        return err
    }

    err = validateKeys(apl.keys, apl.password)
    if err != nil {
        return err
    }

    apl.multiaccount, err = apl.multiaccountsDB.GetAccount(apl.keyUID)
    if err != nil {
        return err
    }

    return nil
}

/*
|--------------------------------------------------------------------------
| RawMessagePayload
|--------------------------------------------------------------------------
|
| RawMessagePayloadMounter and RawMessageLoader
|
*/

// NewRawMessagePayloadMounter generates a new and initialised RawMessagePayload flavoured BasePayloadMounter
// responsible for the whole lifecycle of an RawMessagePayload
func NewRawMessagePayloadMounter(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *SenderConfig) *BasePayloadMounter {
    pe = pe.Renew()
    payload := NewRawMessagesPayload()

    return NewBasePayloadMounter(
        NewRawMessageLoader(backend, payload, config),
        NewRawMessagePayloadMarshaller(payload),
        pe,
    )
}

type RawMessageLoader struct {
    payload               *RawMessagesPayload
    syncRawMessageHandler *SyncRawMessageHandler
    keyUID                string
    deviceType            string
}

func NewRawMessageLoader(backend *api.GethStatusBackend, payload *RawMessagesPayload, config *SenderConfig) *RawMessageLoader {
    return &RawMessageLoader{
        syncRawMessageHandler: NewSyncRawMessageHandler(backend),
        payload:               payload,
        keyUID:                config.KeyUID,
        deviceType:            config.DeviceType,
    }
}

func (r *RawMessageLoader) Load() (err error) {
    r.payload.rawMessages, r.payload.profileKeypair, r.payload.setting, err = r.syncRawMessageHandler.PrepareRawMessage(r.keyUID, r.deviceType)
    return err
}

/*
|--------------------------------------------------------------------------
| InstallationPayload
|--------------------------------------------------------------------------
|
| InstallationPayloadMounter and InstallationPayloadLoader
|
*/

// NewInstallationPayloadMounter generates a new and initialised InstallationPayload flavoured BasePayloadMounter
// responsible for the whole lifecycle of an InstallationPayload
func NewInstallationPayloadMounter(pe *PayloadEncryptor, backend *api.GethStatusBackend, deviceType string) *BasePayloadMounter {
    pe = pe.Renew()
    payload := NewRawMessagesPayload()

    return NewBasePayloadMounter(
        NewInstallationPayloadLoader(backend, payload, deviceType),
        NewRawMessagePayloadMarshaller(payload),
        pe,
    )
}

type InstallationPayloadLoader struct {
    payload               *RawMessagesPayload
    syncRawMessageHandler *SyncRawMessageHandler
    deviceType            string
}

func NewInstallationPayloadLoader(backend *api.GethStatusBackend, payload *RawMessagesPayload, deviceType string) *InstallationPayloadLoader {
    return &InstallationPayloadLoader{
        payload:               payload,
        syncRawMessageHandler: NewSyncRawMessageHandler(backend),
        deviceType:            deviceType,
    }
}

func (r *InstallationPayloadLoader) Load() error {
    rawMessageCollector := new(RawMessageCollector)
    err := r.syncRawMessageHandler.CollectInstallationData(rawMessageCollector, r.deviceType)
    if err != nil {
        return err
    }
    rms := rawMessageCollector.convertToSyncRawMessage()
    r.payload.rawMessages = rms.RawMessages
    return nil
}

/*
|--------------------------------------------------------------------------
| PayloadMounters
|--------------------------------------------------------------------------
|
| Funcs for all PayloadMounters AccountPayloadMounter, RawMessagePayloadMounter and InstallationPayloadMounter
|
*/

// NewPayloadMounters returns PayloadMounter s configured to handle local pairing transfers of:
//   - AccountPayload, RawMessagePayload and InstallationPayload
func NewPayloadMounters(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *SenderConfig) (PayloadMounter, PayloadMounter, PayloadMounterReceiver, error) {
    am, err := NewAccountPayloadMounter(pe, config, logger)
    if err != nil {
        return nil, nil, nil, err
    }
    rmm := NewRawMessagePayloadMounter(logger, pe, backend, config)
    imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
    return am, rmm, imr, nil
}

/*
|--------------------------------------------------------------------------
| KeystoreFilesPayload
|--------------------------------------------------------------------------
*/

func NewKeystoreFilesPayloadMounter(backend *api.GethStatusBackend, pe *PayloadEncryptor, config *KeystoreFilesSenderConfig, logger *zap.Logger) (*BasePayloadMounter, error) {
    l := logger.Named("KeystoreFilesPayloadLoader")
    l.Debug("fired", zap.Any("config", config))

    pe = pe.Renew()

    // A new SHARED AccountPayload
    p := new(AccountPayload)
    kfpl, err := NewKeystoreFilesPayloadLoader(backend, p, config)
    if err != nil {
        return nil, err
    }

    return NewBasePayloadMounter(
        kfpl,
        NewPairingPayloadMarshaller(p, l),
        pe,
    ), nil
}

type KeystoreFilesPayloadLoader struct {
    *AccountPayload

    keystorePath            string
    loggedInKeyUID          string
    keystoreFilesToTransfer []string
}

func NewKeystoreFilesPayloadLoader(backend *api.GethStatusBackend, p *AccountPayload, config *KeystoreFilesSenderConfig) (*KeystoreFilesPayloadLoader, error) {
    if config == nil {
        return nil, fmt.Errorf("empty keystore files sender config")
    }

    kfpl := &KeystoreFilesPayloadLoader{
        AccountPayload: p,
        keystorePath:   config.KeystorePath,
        loggedInKeyUID: config.LoggedInKeyUID,
    }

    kfpl.password = config.Password

    accountService := backend.StatusNode().AccountService()

    for _, keyUID := range config.KeypairsToExport {
        kp, err := accountService.GetKeypairByKeyUID(keyUID)
        if err != nil {
            return nil, err
        }

        if kp.Type == accounts.KeypairTypeSeed {
            kfpl.keystoreFilesToTransfer = append(kfpl.keystoreFilesToTransfer, kp.DerivedFrom[2:])
        }

        for _, acc := range kp.Accounts {
            kfpl.keystoreFilesToTransfer = append(kfpl.keystoreFilesToTransfer, acc.Address.Hex()[2:])
        }
    }

    return kfpl, nil
}

func (kfpl *KeystoreFilesPayloadLoader) Load() error {
    kfpl.keys = make(map[string][]byte)
    err := loadKeys(kfpl.keys, kfpl.keystorePath)
    if err != nil {
        return err
    }

    // Create a new map to filter keys
    filteredMap := make(map[string][]byte)
    for _, searchKey := range kfpl.keystoreFilesToTransfer {
        for key, value := range kfpl.keys {
            if strings.Contains(key, strings.ToLower(searchKey)) {
                filteredMap[key] = value
                break
            }
        }
    }

    kfpl.keys = filteredMap

    return validateKeys(kfpl.keys, kfpl.password)
}