2019-07-08 09:21:21 +00:00
|
|
|
package statusproto
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"log"
|
2019-07-16 10:43:07 +00:00
|
|
|
"path/filepath"
|
2019-07-08 09:21:21 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-07-16 10:43:07 +00:00
|
|
|
whisper "github.com/status-im/whisper/whisperv6"
|
|
|
|
|
|
|
|
"github.com/status-im/status-protocol-go/encryption"
|
|
|
|
"github.com/status-im/status-protocol-go/encryption/multidevice"
|
|
|
|
"github.com/status-im/status-protocol-go/encryption/sharedsecret"
|
|
|
|
migrations "github.com/status-im/status-protocol-go/internal/sqlite"
|
|
|
|
"github.com/status-im/status-protocol-go/sqlite"
|
|
|
|
transport "github.com/status-im/status-protocol-go/transport/whisper"
|
|
|
|
"github.com/status-im/status-protocol-go/transport/whisper/filter"
|
2019-07-08 09:21:21 +00:00
|
|
|
protocol "github.com/status-im/status-protocol-go/v1"
|
|
|
|
)
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
var (
|
|
|
|
ErrChatIDEmpty = errors.New("chat ID is empty")
|
|
|
|
ErrNotImplemented = errors.New("not implemented")
|
|
|
|
)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// Messenger is a entity managing chats and messages.
|
|
|
|
// It acts as a bridge between the application and encryption
|
|
|
|
// layers.
|
|
|
|
// It needs to expose an interface to manage installations
|
|
|
|
// because installations are managed by the user.
|
|
|
|
// Similarly, it needs to expose an interface to manage
|
|
|
|
// mailservers because they can also be managed by the user.
|
|
|
|
type Messenger struct {
|
|
|
|
identity *ecdsa.PrivateKey
|
|
|
|
persistence persistence
|
|
|
|
adapter *whisperAdapter
|
|
|
|
encryptor *encryption.Protocol
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
ownMessages map[string][]*protocol.Message
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type config struct {
|
|
|
|
onNewInstallationsHandler func([]*multidevice.Installation)
|
|
|
|
onNewSharedSecretHandler func([]*sharedsecret.Secret)
|
|
|
|
onSendContactCodeHandler func(*encryption.ProtocolMessageSpec)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
publicChatNames []string
|
|
|
|
publicKeys []*ecdsa.PublicKey
|
|
|
|
secrets []filter.NegotiatedSecret
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type Option func(*config) error
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func WithOnNewInstallationsHandler(h func([]*multidevice.Installation)) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.onNewInstallationsHandler = h
|
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func WithOnNewSharedSecret(h func([]*sharedsecret.Secret)) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.onNewSharedSecretHandler = h
|
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func WithChats(
|
|
|
|
publicChatNames []string,
|
|
|
|
publicKeys []*ecdsa.PublicKey,
|
|
|
|
secrets []filter.NegotiatedSecret,
|
|
|
|
) func(c *config) error {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.publicChatNames = publicChatNames
|
|
|
|
c.publicKeys = publicKeys
|
|
|
|
c.secrets = secrets
|
|
|
|
return nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func NewMessenger(
|
|
|
|
identity *ecdsa.PrivateKey,
|
|
|
|
server transport.Server,
|
|
|
|
shh *whisper.Whisper,
|
|
|
|
dataDir string,
|
|
|
|
dbKey string,
|
|
|
|
installationID string,
|
|
|
|
opts ...Option,
|
|
|
|
) (*Messenger, error) {
|
|
|
|
var messenger *Messenger
|
|
|
|
|
|
|
|
// Set default config fields.
|
|
|
|
c := config{
|
|
|
|
onNewInstallationsHandler: func(installations []*multidevice.Installation) {
|
|
|
|
for _, installation := range installations {
|
|
|
|
log.Printf(
|
|
|
|
"[onNewInstallationsHandler] received a new installation %s from %s",
|
|
|
|
installation.Identity, installation.ID,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onNewSharedSecretHandler: func(secrets []*sharedsecret.Secret) {
|
|
|
|
if err := messenger.handleSharedSecrets(secrets); err != nil {
|
|
|
|
log.Printf("[onNewSharedSecretHandler] failed to process secrets: %v", err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onSendContactCodeHandler: func(messageSpec *encryption.ProtocolMessageSpec) {
|
|
|
|
log.Printf("[onSendContactCodeHandler] received a SendContactCode request")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
_, err := messenger.adapter.SendContactCode(ctx, messageSpec)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("[onSendContactCodeHandler] failed to send a contact code: %v", err)
|
|
|
|
}
|
|
|
|
},
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
for _, opt := range opts {
|
|
|
|
if err := opt(&c); err != nil {
|
|
|
|
return nil, err
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
t, err := transport.NewWhisperServiceTransport(
|
|
|
|
server,
|
|
|
|
shh,
|
|
|
|
identity,
|
|
|
|
dataDir,
|
|
|
|
dbKey,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create a WhisperServiceTransport")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if _, err := t.Init(c.publicChatNames, c.publicKeys, c.secrets); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to initialize WhisperServiceTransport")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
encryptionProtocol, err := encryption.New(
|
|
|
|
dataDir,
|
|
|
|
dbKey,
|
|
|
|
installationID,
|
|
|
|
c.onNewInstallationsHandler,
|
|
|
|
c.onNewSharedSecretHandler,
|
|
|
|
c.onSendContactCodeHandler,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to create the encryption layer")
|
|
|
|
}
|
|
|
|
|
|
|
|
messagesDB, err := sqlite.Open(filepath.Join(dataDir, "messages.sql"), dbKey, sqlite.MigrationConfig{
|
|
|
|
AssetNames: migrations.AssetNames(),
|
|
|
|
AssetGetter: func(name string) ([]byte, error) {
|
|
|
|
return migrations.Asset(name)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to initialize messages db")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
messenger = &Messenger{
|
|
|
|
identity: identity,
|
|
|
|
persistence: &sqlitePersistence{db: messagesDB},
|
|
|
|
adapter: newWhisperAdapter(identity, t, encryptionProtocol),
|
|
|
|
encryptor: encryptionProtocol,
|
|
|
|
ownMessages: make(map[string][]*protocol.Message),
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-17 13:14:16 +00:00
|
|
|
// Start all services immediately.
|
|
|
|
// TODO: consider removing identity as an argument to Start().
|
|
|
|
if err := encryptionProtocol.Start(identity); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
return messenger, nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error {
|
|
|
|
return m.adapter.handleSharedSecrets(secrets)
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) EnableInstallation(id string) error {
|
|
|
|
return m.encryptor.EnableInstallation(&m.identity.PublicKey, id)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) DisableInstallation(id string) error {
|
|
|
|
return m.encryptor.DisableInstallation(&m.identity.PublicKey, id)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Installations() ([]*multidevice.Installation, error) {
|
|
|
|
return m.encryptor.GetOurInstallations(&m.identity.PublicKey)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) SetInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
|
|
|
|
return m.encryptor.SetInstallationMetadata(&m.identity.PublicKey, id, data)
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// NOT_IMPLEMENTED
|
|
|
|
func (m *Messenger) SelectMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// NOT_IMPLEMENTED
|
|
|
|
func (m *Messenger) AddMailserver(enode string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// NOT_IMPLEMENTED
|
|
|
|
func (m *Messenger) RemoveMailserver(id string) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// NOT_IMPLEMENTED
|
|
|
|
func (m *Messenger) Mailservers() ([]string, error) {
|
|
|
|
return nil, ErrNotImplemented
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Join(chat Chat) error {
|
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
return m.adapter.JoinPrivate(chat.PublicKey())
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.JoinPublic(chat.PublicName())
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return errors.New("chat is neither public nor private")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Leave(chat Chat) error {
|
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
return m.adapter.LeavePrivate(chat.PublicKey())
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.LeavePublic(chat.PublicName())
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Send(ctx context.Context, chat Chat, data []byte) ([]byte, error) {
|
|
|
|
chatID := chat.ID()
|
|
|
|
if chatID == "" {
|
|
|
|
return nil, ErrChatIDEmpty
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
clock, err := m.persistence.LastMessageClock(chat.ID())
|
2019-07-08 09:21:21 +00:00
|
|
|
if err != nil {
|
2019-07-16 10:43:07 +00:00
|
|
|
return nil, err
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
hash, message, err := m.adapter.SendPrivate(ctx, chat.PublicKey(), chat.ID(), data, clock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// Save our message because it won't be received from the transport layer.
|
|
|
|
message.ID = hash // a Message need ID to be properly stored in the db
|
|
|
|
message.SigPubKey = &m.identity.PublicKey
|
|
|
|
_, err = m.persistence.SaveMessages(chat.ID(), []*protocol.Message{message})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
// Cache it to be returned in Retrieve().
|
|
|
|
m.ownMessages[chatID] = append(m.ownMessages[chatID], message)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
return hash, nil
|
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
return m.adapter.SendPublic(ctx, chat.PublicName(), chat.ID(), data, clock)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
return nil, errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
type RetrieveConfig struct {
|
|
|
|
From time.Time
|
|
|
|
To time.Time
|
|
|
|
latest bool
|
|
|
|
last24Hours bool
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
var (
|
|
|
|
RetrieveLatest = RetrieveConfig{latest: true}
|
|
|
|
RetrieveLastDay = RetrieveConfig{latest: true, last24Hours: true}
|
|
|
|
)
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) Retrieve(ctx context.Context, chat Chat, c RetrieveConfig) (messages []*protocol.Message, err error) {
|
|
|
|
var latest []*protocol.Message
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if chat.PublicKey() != nil {
|
|
|
|
latest, err = m.adapter.RetrievePrivateMessages(chat.PublicKey())
|
|
|
|
// Return any own messages for this chat as well.
|
|
|
|
if ownMessages, ok := m.ownMessages[chat.ID()]; ok {
|
|
|
|
latest = append(latest, ownMessages...)
|
|
|
|
delete(m.ownMessages, chat.ID())
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
2019-07-16 10:43:07 +00:00
|
|
|
} else if chat.PublicName() != "" {
|
|
|
|
latest, err = m.adapter.RetrievePublicMessages(chat.PublicName())
|
|
|
|
} else {
|
|
|
|
return nil, errors.New("chat is neither public nor private")
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
if err != nil {
|
|
|
|
err = errors.Wrap(err, "failed to retrieve messages")
|
|
|
|
return
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
_, err = m.persistence.SaveMessages(chat.ID(), latest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to save latest messages")
|
|
|
|
}
|
2019-07-08 09:21:21 +00:00
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
return m.retrieveMessages(ctx, chat, c, latest)
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2019-07-16 10:43:07 +00:00
|
|
|
func (m *Messenger) retrieveMessages(ctx context.Context, chat Chat, c RetrieveConfig, latest []*protocol.Message) (messages []*protocol.Message, err error) {
|
|
|
|
if !c.latest {
|
|
|
|
return m.persistence.Messages(chat.ID(), c.From, c.To)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.last24Hours {
|
|
|
|
to := time.Now()
|
|
|
|
from := to.Add(-time.Hour * 24)
|
|
|
|
return m.persistence.Messages(chat.ID(), from, to)
|
|
|
|
}
|
|
|
|
|
|
|
|
return latest, nil
|
2019-07-08 09:21:21 +00:00
|
|
|
}
|