1384 lines
41 KiB
Go
1384 lines
41 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"database/sql"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/pkg/errors"
|
|
datasyncnode "github.com/status-im/mvds/node"
|
|
datasyncproto "github.com/status-im/mvds/protobuf"
|
|
"github.com/status-im/mvds/state"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/protocol/datasync"
|
|
datasyncpeer "github.com/status-im/status-go/protocol/datasync/peer"
|
|
"github.com/status-im/status-go/protocol/encryption"
|
|
"github.com/status-im/status-go/protocol/encryption/sharedsecret"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/protocol/transport"
|
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
|
)
|
|
|
|
// Whisper message properties.
|
|
const (
|
|
whisperTTL = 15
|
|
whisperDefaultPoW = 0.002
|
|
// whisperLargeSizePoW is the PoWTarget for larger payload sizes
|
|
whisperLargeSizePoW = 0.000002
|
|
// largeSizeInBytes is when should we be using a lower POW.
|
|
// Roughly this is 50KB
|
|
largeSizeInBytes = 50000
|
|
whisperPoWTime = 5
|
|
maxMessageSenderEphemeralKeys = 3
|
|
)
|
|
|
|
// RekeyCompatibility indicates whether we should be sending
|
|
// keys in 1-to-1 messages as well as in the newer format
|
|
var RekeyCompatibility = true
|
|
|
|
// SentMessage reprent a message that has been passed to the transport layer
|
|
type SentMessage struct {
|
|
PublicKey *ecdsa.PublicKey
|
|
Spec *encryption.ProtocolMessageSpec
|
|
MessageIDs [][]byte
|
|
}
|
|
|
|
type MessageEventType uint32
|
|
|
|
const (
|
|
MessageScheduled = iota + 1
|
|
MessageSent
|
|
)
|
|
|
|
type MessageEvent struct {
|
|
Recipient *ecdsa.PublicKey
|
|
Type MessageEventType
|
|
SentMessage *SentMessage
|
|
RawMessage *RawMessage
|
|
}
|
|
|
|
type MessageSender struct {
|
|
identity *ecdsa.PrivateKey
|
|
datasync *datasync.DataSync
|
|
database *sql.DB
|
|
protocol *encryption.Protocol
|
|
transport *transport.Transport
|
|
logger *zap.Logger
|
|
persistence *RawMessagesPersistence
|
|
|
|
datasyncEnabled bool
|
|
|
|
// ephemeralKeys is a map that contains the ephemeral keys of the client, used
|
|
// to decrypt messages
|
|
ephemeralKeys map[string]*ecdsa.PrivateKey
|
|
ephemeralKeysMutex sync.Mutex
|
|
|
|
// messageEventsSubscriptions contains all the subscriptions for message events
|
|
messageEventsSubscriptions []chan<- *MessageEvent
|
|
messageEventsSubscriptionsMutex sync.Mutex
|
|
|
|
featureFlags FeatureFlags
|
|
|
|
// handleSharedSecrets is a callback that is called every time a new shared secret is negotiated
|
|
handleSharedSecrets func([]*sharedsecret.Secret) error
|
|
}
|
|
|
|
func NewMessageSender(
|
|
identity *ecdsa.PrivateKey,
|
|
database *sql.DB,
|
|
enc *encryption.Protocol,
|
|
transport *transport.Transport,
|
|
logger *zap.Logger,
|
|
features FeatureFlags,
|
|
) (*MessageSender, error) {
|
|
p := &MessageSender{
|
|
identity: identity,
|
|
datasyncEnabled: features.Datasync,
|
|
protocol: enc,
|
|
database: database,
|
|
persistence: NewRawMessagesPersistence(database),
|
|
transport: transport,
|
|
logger: logger,
|
|
ephemeralKeys: make(map[string]*ecdsa.PrivateKey),
|
|
featureFlags: features,
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (s *MessageSender) Stop() {
|
|
s.messageEventsSubscriptionsMutex.Lock()
|
|
defer s.messageEventsSubscriptionsMutex.Unlock()
|
|
|
|
for _, c := range s.messageEventsSubscriptions {
|
|
close(c)
|
|
}
|
|
s.messageEventsSubscriptions = nil
|
|
s.StopDatasync()
|
|
}
|
|
|
|
func (s *MessageSender) SetHandleSharedSecrets(handler func([]*sharedsecret.Secret) error) {
|
|
s.handleSharedSecrets = handler
|
|
}
|
|
|
|
func (s *MessageSender) StartDatasync(statusChangeEvent chan datasyncnode.PeerStatusChangeEvent, handler func(peer state.PeerID, payload *datasyncproto.Payload) error) error {
|
|
if !s.datasyncEnabled {
|
|
return nil
|
|
}
|
|
|
|
dataSyncTransport := datasync.NewNodeTransport()
|
|
dataSyncNode, err := datasyncnode.NewPersistentNode(
|
|
s.database,
|
|
dataSyncTransport,
|
|
datasyncpeer.PublicKeyToPeerID(s.identity.PublicKey),
|
|
datasyncnode.BATCH,
|
|
datasync.CalculateSendTime,
|
|
statusChangeEvent,
|
|
s.logger,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.datasync = datasync.New(dataSyncNode, dataSyncTransport, true, s.logger)
|
|
|
|
s.datasync.Init(handler, s.logger)
|
|
s.datasync.Start(datasync.DatasyncTicker)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SendPrivate takes encoded data, encrypts it and sends through the wire.
|
|
func (s *MessageSender) SendPrivate(
|
|
ctx context.Context,
|
|
recipient *ecdsa.PublicKey,
|
|
rawMessage *RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug(
|
|
"sending a private message",
|
|
zap.String("public-key", types.EncodeHex(crypto.FromECDSAPub(recipient))),
|
|
zap.String("site", "SendPrivate"),
|
|
)
|
|
// Currently we don't support sending through datasync and setting custom waku fields,
|
|
// as the datasync interface is not rich enough to propagate that information, so we
|
|
// would have to add some complexity to handle this.
|
|
if rawMessage.ResendType == ResendTypeDataSync && (rawMessage.Sender != nil || rawMessage.SkipEncryptionLayer || rawMessage.SendOnPersonalTopic) {
|
|
return nil, errors.New("setting identity, skip-encryption or personal topic and datasync not supported")
|
|
}
|
|
|
|
// Set sender identity if not specified
|
|
if rawMessage.Sender == nil {
|
|
rawMessage.Sender = s.identity
|
|
}
|
|
|
|
return s.sendPrivate(ctx, recipient, rawMessage)
|
|
}
|
|
|
|
// SendCommunityMessage takes encoded data, encrypts it and sends through the wire
|
|
// using the community topic and their key
|
|
func (s *MessageSender) SendCommunityMessage(
|
|
ctx context.Context,
|
|
rawMessage *RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug(
|
|
"sending a community message",
|
|
zap.String("communityId", types.EncodeHex(rawMessage.CommunityID)),
|
|
zap.String("site", "SendCommunityMessage"),
|
|
)
|
|
rawMessage.Sender = s.identity
|
|
|
|
return s.sendCommunity(ctx, rawMessage)
|
|
}
|
|
|
|
// SendPubsubTopicKey sends the protected topic key for a community to a list of recipients
|
|
func (s *MessageSender) SendPubsubTopicKey(
|
|
ctx context.Context,
|
|
rawMessage *RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug(
|
|
"sending the protected topic key for a community",
|
|
zap.String("communityId", types.EncodeHex(rawMessage.CommunityID)),
|
|
zap.String("site", "SendPubsubTopicKey"),
|
|
)
|
|
rawMessage.Sender = s.identity
|
|
messageID, err := s.getMessageID(rawMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = s.setMessageID(messageID, rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Notify before dispatching, otherwise the dispatch subscription might happen
|
|
// earlier than the scheduled
|
|
s.notifyOnScheduledMessage(nil, rawMessage)
|
|
|
|
// Send to each recipients
|
|
for _, recipient := range rawMessage.Recipients {
|
|
_, err = s.sendPrivate(ctx, recipient, rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to send message")
|
|
}
|
|
}
|
|
return messageID, nil
|
|
|
|
}
|
|
|
|
// SendGroup takes encoded data, encrypts it and sends through the wire,
|
|
// always return the messageID
|
|
func (s *MessageSender) SendGroup(
|
|
ctx context.Context,
|
|
recipients []*ecdsa.PublicKey,
|
|
rawMessage RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug(
|
|
"sending a private group message",
|
|
zap.String("site", "SendGroup"),
|
|
)
|
|
// Set sender if not specified
|
|
if rawMessage.Sender == nil {
|
|
rawMessage.Sender = s.identity
|
|
}
|
|
|
|
// Calculate messageID first and set on raw message
|
|
messageID, err := s.getMessageID(&rawMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = s.setMessageID(messageID, &rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We call it only once, and we nil the function after so it doesn't get called again
|
|
if rawMessage.BeforeDispatch != nil {
|
|
if err := rawMessage.BeforeDispatch(&rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Send to each recipients
|
|
for _, recipient := range recipients {
|
|
_, err = s.sendPrivate(ctx, recipient, &rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to send message")
|
|
}
|
|
}
|
|
return messageID, nil
|
|
}
|
|
|
|
func (s *MessageSender) getMessageID(rawMessage *RawMessage) (types.HexBytes, error) {
|
|
wrappedMessage, err := s.wrapMessageV1(rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap message")
|
|
}
|
|
|
|
messageID := v1protocol.MessageID(&rawMessage.Sender.PublicKey, wrappedMessage)
|
|
return messageID, nil
|
|
}
|
|
|
|
func (s *MessageSender) ValidateRawMessage(rawMessage *RawMessage) error {
|
|
id, err := s.getMessageID(rawMessage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
messageID := types.EncodeHex(id)
|
|
|
|
return s.validateMessageID(messageID, rawMessage)
|
|
|
|
}
|
|
|
|
func (s *MessageSender) validateMessageID(messageID string, rawMessage *RawMessage) error {
|
|
if len(rawMessage.ID) > 0 && rawMessage.ID != messageID {
|
|
s.logger.Error("failed to validate message ID, RawMessage content was modified",
|
|
zap.String("prevID", rawMessage.ID),
|
|
zap.String("newID", messageID),
|
|
zap.Any("contentType", rawMessage.MessageType))
|
|
return ErrModifiedRawMessage
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *MessageSender) setMessageID(messageID types.HexBytes, rawMessage *RawMessage) error {
|
|
msgID := types.EncodeHex(messageID)
|
|
|
|
if err := s.validateMessageID(msgID, rawMessage); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawMessage.ID = msgID
|
|
return nil
|
|
}
|
|
|
|
func ShouldCommunityMessageBeEncrypted(msgType protobuf.ApplicationMetadataMessage_Type) bool {
|
|
return msgType == protobuf.ApplicationMetadataMessage_CHAT_MESSAGE ||
|
|
msgType == protobuf.ApplicationMetadataMessage_EDIT_MESSAGE ||
|
|
msgType == protobuf.ApplicationMetadataMessage_DELETE_MESSAGE ||
|
|
msgType == protobuf.ApplicationMetadataMessage_PIN_MESSAGE ||
|
|
msgType == protobuf.ApplicationMetadataMessage_EMOJI_REACTION
|
|
}
|
|
|
|
// sendCommunity sends a message that's to be sent in a community
|
|
// If it's a chat message, it will go to the respective topic derived by the
|
|
// chat id, if it's not a chat message, it will go to the community topic.
|
|
func (s *MessageSender) sendCommunity(
|
|
ctx context.Context,
|
|
rawMessage *RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug("sending community message", zap.String("recipient", types.EncodeHex(crypto.FromECDSAPub(&rawMessage.Sender.PublicKey))))
|
|
|
|
// Set sender
|
|
if rawMessage.Sender == nil {
|
|
rawMessage.Sender = s.identity
|
|
}
|
|
|
|
messageID, err := s.getMessageID(rawMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = s.setMessageID(messageID, rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rawMessage.BeforeDispatch != nil {
|
|
if err := rawMessage.BeforeDispatch(rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Notify before dispatching, otherwise the dispatch subscription might happen
|
|
// earlier than the scheduled
|
|
s.notifyOnScheduledMessage(nil, rawMessage)
|
|
|
|
var hashes [][]byte
|
|
var newMessages []*types.NewMessage
|
|
|
|
forceRekey := rawMessage.CommunityKeyExMsgType == KeyExMsgRekey
|
|
|
|
// Check if it's a key exchange message. In this case we send it
|
|
// to all the recipients
|
|
if rawMessage.CommunityKeyExMsgType != KeyExMsgNone {
|
|
// If rekeycompatibility is on, we always
|
|
// want to execute below, otherwise we execute
|
|
// only when we want to fill up old keys to a given user
|
|
if RekeyCompatibility || !forceRekey {
|
|
keyExMessageSpecs, err := s.protocol.GetKeyExMessageSpecs(rawMessage.HashRatchetGroupID, s.identity, rawMessage.Recipients, forceRekey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, spec := range keyExMessageSpecs {
|
|
recipient := rawMessage.Recipients[i]
|
|
_, _, err = s.sendMessageSpec(ctx, recipient, spec, [][]byte{messageID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wrappedMessage, err := s.wrapMessageV1(rawMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If it's a chat message, we send it on the community chat topic
|
|
if ShouldCommunityMessageBeEncrypted(rawMessage.MessageType) {
|
|
messageSpec, err := s.protocol.BuildHashRatchetMessage(rawMessage.HashRatchetGroupID, wrappedMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payload, err := proto.Marshal(messageSpec.Message)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal")
|
|
}
|
|
hashes, newMessages, err = s.dispatchCommunityChatMessage(ctx, rawMessage, payload, forceRekey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sentMessage := &SentMessage{
|
|
Spec: messageSpec,
|
|
MessageIDs: [][]byte{messageID},
|
|
}
|
|
|
|
s.notifyOnSentMessage(sentMessage)
|
|
|
|
} else {
|
|
|
|
pubkey, err := crypto.DecompressPubkey(rawMessage.CommunityID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to decompress pubkey")
|
|
}
|
|
hashes, newMessages, err = s.dispatchCommunityMessage(ctx, pubkey, wrappedMessage, rawMessage.PubsubTopic, forceRekey, rawMessage)
|
|
if err != nil {
|
|
s.logger.Error("failed to send a community message", zap.Error(err))
|
|
return nil, errors.Wrap(err, "failed to send a message spec")
|
|
}
|
|
}
|
|
|
|
s.logger.Debug("sent-message: community ",
|
|
zap.Strings("recipient", PubkeysToHex(rawMessage.Recipients)),
|
|
zap.String("messageID", messageID.String()),
|
|
zap.String("messageType", "community"),
|
|
zap.Any("contentType", rawMessage.MessageType),
|
|
zap.Strings("hashes", types.EncodeHexes(hashes)))
|
|
s.transport.Track(messageID, hashes, newMessages)
|
|
|
|
return messageID, nil
|
|
}
|
|
|
|
// sendPrivate sends data to the recipient identifying with a given public key.
|
|
func (s *MessageSender) sendPrivate(
|
|
ctx context.Context,
|
|
recipient *ecdsa.PublicKey,
|
|
rawMessage *RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug("sending private message", zap.String("recipient", types.EncodeHex(crypto.FromECDSAPub(recipient))))
|
|
|
|
var wrappedMessage []byte
|
|
var err error
|
|
if rawMessage.SkipApplicationWrap {
|
|
wrappedMessage = rawMessage.Payload
|
|
} else {
|
|
wrappedMessage, err = s.wrapMessageV1(rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap message")
|
|
}
|
|
}
|
|
|
|
messageID := v1protocol.MessageID(&rawMessage.Sender.PublicKey, wrappedMessage)
|
|
|
|
if err = s.setMessageID(messageID, rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rawMessage.BeforeDispatch != nil {
|
|
if err := rawMessage.BeforeDispatch(rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Notify before dispatching, otherwise the dispatch subscription might happen
|
|
// earlier than the scheduled
|
|
s.notifyOnScheduledMessage(recipient, rawMessage)
|
|
|
|
if s.datasync != nil && s.featureFlags.Datasync && rawMessage.ResendType == ResendTypeDataSync {
|
|
// No need to call transport tracking.
|
|
// It is done in a data sync dispatch step.
|
|
datasyncID, err := s.addToDataSync(recipient, wrappedMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to send message with datasync")
|
|
}
|
|
// We don't need to receive confirmations from our own devices
|
|
if !IsPubKeyEqual(recipient, &s.identity.PublicKey) {
|
|
confirmation := &RawMessageConfirmation{
|
|
DataSyncID: datasyncID,
|
|
MessageID: messageID,
|
|
PublicKey: crypto.CompressPubkey(recipient),
|
|
}
|
|
|
|
err = s.persistence.InsertPendingConfirmation(confirmation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else if rawMessage.SkipEncryptionLayer {
|
|
|
|
messageBytes := wrappedMessage
|
|
if rawMessage.CommunityKeyExMsgType == KeyExMsgReuse {
|
|
groupID := rawMessage.HashRatchetGroupID
|
|
|
|
ratchets, err := s.protocol.GetKeysForGroup(groupID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
message, err := s.protocol.BuildHashRatchetKeyExchangeMessageWithPayload(s.identity, recipient, groupID, ratchets, wrappedMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
messageBytes, err = proto.Marshal(message.Message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// When SkipProtocolLayer is set we don't pass the message to the encryption layer
|
|
hashes, newMessages, err := s.sendPrivateRawMessage(ctx, rawMessage, recipient, messageBytes)
|
|
if err != nil {
|
|
s.logger.Error("failed to send a private message", zap.Error(err))
|
|
return nil, errors.Wrap(err, "failed to send a message spec")
|
|
}
|
|
|
|
s.logger.Debug("sent-message: private skipProtocolLayer",
|
|
zap.String("recipient", PubkeyToHex(recipient)),
|
|
zap.String("messageID", messageID.String()),
|
|
zap.String("messageType", "private"),
|
|
zap.Any("contentType", rawMessage.MessageType),
|
|
zap.Strings("hashes", types.EncodeHexes(hashes)))
|
|
s.transport.Track(messageID, hashes, newMessages)
|
|
|
|
} else {
|
|
messageSpec, err := s.protocol.BuildEncryptedMessage(rawMessage.Sender, recipient, wrappedMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to encrypt message")
|
|
}
|
|
|
|
hashes, newMessages, err := s.sendMessageSpec(ctx, recipient, messageSpec, [][]byte{messageID})
|
|
if err != nil {
|
|
s.logger.Error("failed to send a private message", zap.Error(err))
|
|
return nil, errors.Wrap(err, "failed to send a message spec")
|
|
}
|
|
|
|
s.logger.Debug("sent-message: private without datasync",
|
|
zap.String("recipient", PubkeyToHex(recipient)),
|
|
zap.String("messageID", messageID.String()),
|
|
zap.Any("contentType", rawMessage.MessageType),
|
|
zap.String("messageType", "private"),
|
|
zap.Strings("hashes", types.EncodeHexes(hashes)))
|
|
s.transport.Track(messageID, hashes, newMessages)
|
|
}
|
|
|
|
return messageID, nil
|
|
}
|
|
|
|
// sendPairInstallation sends data to the recipients, using DH
|
|
func (s *MessageSender) SendPairInstallation(
|
|
ctx context.Context,
|
|
recipient *ecdsa.PublicKey,
|
|
rawMessage RawMessage,
|
|
) ([]byte, error) {
|
|
s.logger.Debug("sending private message", zap.String("recipient", types.EncodeHex(crypto.FromECDSAPub(recipient))))
|
|
|
|
wrappedMessage, err := s.wrapMessageV1(&rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap message")
|
|
}
|
|
|
|
messageSpec, err := s.protocol.BuildDHMessage(s.identity, recipient, wrappedMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to encrypt message")
|
|
}
|
|
|
|
messageID := v1protocol.MessageID(&s.identity.PublicKey, wrappedMessage)
|
|
|
|
hashes, newMessages, err := s.sendMessageSpec(ctx, recipient, messageSpec, [][]byte{messageID})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to send a message spec")
|
|
}
|
|
|
|
s.transport.Track(messageID, hashes, newMessages)
|
|
|
|
return messageID, nil
|
|
}
|
|
|
|
func (s *MessageSender) encodeMembershipUpdate(
|
|
message v1protocol.MembershipUpdateMessage,
|
|
chatEntity ChatEntity,
|
|
) ([]byte, error) {
|
|
|
|
if chatEntity != nil {
|
|
chatEntityProtobuf := chatEntity.GetProtobuf()
|
|
switch chatEntityProtobuf := chatEntityProtobuf.(type) {
|
|
case *protobuf.ChatMessage:
|
|
message.Message = chatEntityProtobuf
|
|
case *protobuf.EmojiReaction:
|
|
message.EmojiReaction = chatEntityProtobuf
|
|
|
|
}
|
|
}
|
|
|
|
encodedMessage, err := v1protocol.EncodeMembershipUpdateMessage(message)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to encode membership update message")
|
|
}
|
|
|
|
return encodedMessage, nil
|
|
}
|
|
|
|
// EncodeMembershipUpdate takes a group and an optional chat message and returns the protobuf representation to be sent on the wire.
|
|
// All the events in a group are encoded and added to the payload
|
|
func (s *MessageSender) EncodeMembershipUpdate(
|
|
group *v1protocol.Group,
|
|
chatEntity ChatEntity,
|
|
) ([]byte, error) {
|
|
message := v1protocol.MembershipUpdateMessage{
|
|
ChatID: group.ChatID(),
|
|
Events: group.Events(),
|
|
}
|
|
|
|
return s.encodeMembershipUpdate(message, chatEntity)
|
|
}
|
|
|
|
// EncodeAbridgedMembershipUpdate takes a group and an optional chat message and returns the protobuf representation to be sent on the wire.
|
|
// Only the events relevant to the current group are encoded
|
|
func (s *MessageSender) EncodeAbridgedMembershipUpdate(
|
|
group *v1protocol.Group,
|
|
chatEntity ChatEntity,
|
|
) ([]byte, error) {
|
|
message := v1protocol.MembershipUpdateMessage{
|
|
ChatID: group.ChatID(),
|
|
Events: group.AbridgedEvents(),
|
|
}
|
|
return s.encodeMembershipUpdate(message, chatEntity)
|
|
}
|
|
|
|
func (s *MessageSender) dispatchCommunityChatMessage(ctx context.Context, rawMessage *RawMessage, wrappedMessage []byte, rekey bool) ([][]byte, []*types.NewMessage, error) {
|
|
payload := wrappedMessage
|
|
var err error
|
|
if rekey && len(rawMessage.HashRatchetGroupID) != 0 {
|
|
|
|
var ratchet *encryption.HashRatchetKeyCompatibility
|
|
// We have just rekeyed, pull the latest
|
|
if RekeyCompatibility {
|
|
ratchet, err = s.protocol.GetCurrentKeyForGroup(rawMessage.HashRatchetGroupID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
}
|
|
// We send the message over the community topic
|
|
spec, err := s.protocol.BuildHashRatchetReKeyGroupMessage(s.identity, rawMessage.Recipients, rawMessage.HashRatchetGroupID, wrappedMessage, ratchet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
payload, err = proto.Marshal(spec.Message)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
newMessage := &types.NewMessage{
|
|
TTL: whisperTTL,
|
|
Payload: payload,
|
|
PowTarget: calculatePoW(payload),
|
|
PowTime: whisperPoWTime,
|
|
PubsubTopic: rawMessage.PubsubTopic,
|
|
}
|
|
|
|
if rawMessage.BeforeDispatch != nil {
|
|
if err := rawMessage.BeforeDispatch(rawMessage); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// notify before dispatching
|
|
s.notifyOnScheduledMessage(nil, rawMessage)
|
|
|
|
newMessages, err := s.segmentMessage(newMessage)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hashes := make([][]byte, 0, len(newMessages))
|
|
for _, newMessage := range newMessages {
|
|
hash, err := s.transport.SendPublic(ctx, newMessage, rawMessage.LocalChatID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
hashes = append(hashes, hash)
|
|
}
|
|
|
|
return hashes, newMessages, nil
|
|
}
|
|
|
|
// SendPublic takes encoded data, encrypts it and sends through the wire.
|
|
func (s *MessageSender) SendPublic(
|
|
ctx context.Context,
|
|
chatName string,
|
|
rawMessage RawMessage,
|
|
) ([]byte, error) {
|
|
// Set sender
|
|
if rawMessage.Sender == nil {
|
|
rawMessage.Sender = s.identity
|
|
}
|
|
|
|
var wrappedMessage []byte
|
|
var err error
|
|
if rawMessage.SkipApplicationWrap {
|
|
wrappedMessage = rawMessage.Payload
|
|
} else {
|
|
wrappedMessage, err = s.wrapMessageV1(&rawMessage)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap message")
|
|
}
|
|
}
|
|
|
|
var newMessage *types.NewMessage
|
|
|
|
messageSpec, err := s.protocol.BuildPublicMessage(s.identity, wrappedMessage)
|
|
if err != nil {
|
|
s.logger.Error("failed to send a public message", zap.Error(err))
|
|
return nil, errors.Wrap(err, "failed to wrap a public message in the encryption layer")
|
|
}
|
|
|
|
if len(rawMessage.HashRatchetGroupID) != 0 {
|
|
|
|
var ratchet *encryption.HashRatchetKeyCompatibility
|
|
var err error
|
|
// We have just rekeyed, pull the latest
|
|
ratchet, err = s.protocol.GetCurrentKeyForGroup(rawMessage.HashRatchetGroupID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keyID, err := ratchet.GetKeyID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.logger.Debug("adding key id to message", zap.String("keyid", types.Bytes2Hex(keyID)))
|
|
// We send the message over the community topic
|
|
spec, err := s.protocol.BuildHashRatchetReKeyGroupMessage(s.identity, rawMessage.Recipients, rawMessage.HashRatchetGroupID, wrappedMessage, ratchet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newMessage, err = MessageSpecToWhisper(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
} else if !rawMessage.SkipEncryptionLayer {
|
|
newMessage, err = MessageSpecToWhisper(messageSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
newMessage = &types.NewMessage{
|
|
TTL: whisperTTL,
|
|
Payload: wrappedMessage,
|
|
PowTarget: calculatePoW(wrappedMessage),
|
|
PowTime: whisperPoWTime,
|
|
}
|
|
}
|
|
|
|
newMessage.Ephemeral = rawMessage.Ephemeral
|
|
newMessage.PubsubTopic = rawMessage.PubsubTopic
|
|
newMessage.Priority = rawMessage.Priority
|
|
|
|
messageID := v1protocol.MessageID(&rawMessage.Sender.PublicKey, wrappedMessage)
|
|
|
|
if err = s.setMessageID(messageID, &rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rawMessage.BeforeDispatch != nil {
|
|
if err := rawMessage.BeforeDispatch(&rawMessage); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// notify before dispatching
|
|
s.notifyOnScheduledMessage(nil, &rawMessage)
|
|
|
|
newMessages, err := s.segmentMessage(newMessage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hashes := make([][]byte, 0, len(newMessages))
|
|
for _, newMessage := range newMessages {
|
|
hash, err := s.transport.SendPublic(ctx, newMessage, chatName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hashes = append(hashes, hash)
|
|
}
|
|
|
|
sentMessage := &SentMessage{
|
|
Spec: messageSpec,
|
|
MessageIDs: [][]byte{messageID},
|
|
}
|
|
|
|
s.notifyOnSentMessage(sentMessage)
|
|
|
|
s.logger.Debug("sent-message: public message",
|
|
zap.Strings("recipient", PubkeysToHex(rawMessage.Recipients)),
|
|
zap.String("messageID", messageID.String()),
|
|
zap.Any("contentType", rawMessage.MessageType),
|
|
zap.String("messageType", "public"),
|
|
zap.Strings("hashes", types.EncodeHexes(hashes)))
|
|
s.transport.Track(messageID, hashes, newMessages)
|
|
|
|
return messageID, nil
|
|
}
|
|
|
|
// unwrapDatasyncMessage tries to unwrap message as datasync one and in case of success
|
|
// returns cloned messages with replaced payloads
|
|
func (s *MessageSender) unwrapDatasyncMessage(m *v1protocol.StatusMessage, response *handleMessageResponse) error {
|
|
|
|
datasyncMessage, err := s.datasync.Unwrap(
|
|
m.SigPubKey(),
|
|
m.EncryptionLayer.Payload,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
response.DatasyncSender = m.SigPubKey()
|
|
response.DatasyncAcks = append(response.DatasyncAcks, datasyncMessage.Acks...)
|
|
response.DatasyncRequests = append(response.DatasyncRequests, datasyncMessage.Requests...)
|
|
for _, o := range datasyncMessage.GroupOffers {
|
|
for _, mID := range o.MessageIds {
|
|
response.DatasyncOffers = append(response.DatasyncOffers, DatasyncOffer{GroupID: o.GroupId, MessageID: mID})
|
|
}
|
|
}
|
|
|
|
for _, ds := range datasyncMessage.Messages {
|
|
message, err := m.Clone()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message.EncryptionLayer.Payload = ds.Body
|
|
response.DatasyncMessages = append(response.DatasyncMessages, message)
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HandleMessages expects a whisper message as input, and it will go through
|
|
// a series of transformations until the message is parsed into an application
|
|
// layer message, or in case of Raw methods, the processing stops at the layer
|
|
// before.
|
|
// It returns an error only if the processing of required steps failed.
|
|
func (s *MessageSender) HandleMessages(wakuMessage *types.Message) (*HandleMessageResponse, error) {
|
|
logger := s.logger.With(zap.String("site", "HandleMessages"))
|
|
hlogger := logger.With(zap.String("hash", types.HexBytes(wakuMessage.Hash).String()))
|
|
|
|
response, err := s.handleMessage(wakuMessage)
|
|
if err != nil {
|
|
// Hash ratchet with a group id not found yet, save the message for future processing
|
|
if err == encryption.ErrHashRatchetGroupIDNotFound && len(response.Message.EncryptionLayer.HashRatchetInfo) == 1 {
|
|
info := response.Message.EncryptionLayer.HashRatchetInfo[0]
|
|
return nil, s.persistence.SaveHashRatchetMessage(info.GroupID, info.KeyID, wakuMessage)
|
|
}
|
|
|
|
// The current message segment has been successfully retrieved.
|
|
// However, the collection of segments is not yet complete.
|
|
if err == ErrMessageSegmentsIncomplete {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
// Process queued hash ratchet messages
|
|
for _, hashRatchetInfo := range response.Message.EncryptionLayer.HashRatchetInfo {
|
|
messages, err := s.persistence.GetHashRatchetMessages(hashRatchetInfo.KeyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var processedIds [][]byte
|
|
for _, message := range messages {
|
|
hlogger.Info("handling out of order encrypted messages", zap.String("hash", types.Bytes2Hex(message.Hash)))
|
|
r, err := s.handleMessage(message)
|
|
if err != nil {
|
|
hlogger.Debug("failed to handle hash ratchet message", zap.Error(err))
|
|
continue
|
|
}
|
|
response.DatasyncMessages = append(response.toPublicResponse().StatusMessages, r.Messages()...)
|
|
response.DatasyncAcks = append(response.DatasyncAcks, r.DatasyncAcks...)
|
|
|
|
processedIds = append(processedIds, message.Hash)
|
|
}
|
|
|
|
err = s.persistence.DeleteHashRatchetMessages(processedIds)
|
|
if err != nil {
|
|
s.logger.Warn("failed to delete hash ratchet messages", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return response.toPublicResponse(), nil
|
|
}
|
|
|
|
type DatasyncOffer struct {
|
|
GroupID []byte
|
|
MessageID []byte
|
|
}
|
|
|
|
type HandleMessageResponse struct {
|
|
Hash []byte
|
|
StatusMessages []*v1protocol.StatusMessage
|
|
DatasyncSender *ecdsa.PublicKey
|
|
DatasyncAcks [][]byte
|
|
DatasyncOffers []DatasyncOffer
|
|
DatasyncRequests [][]byte
|
|
}
|
|
|
|
func (h *handleMessageResponse) toPublicResponse() *HandleMessageResponse {
|
|
return &HandleMessageResponse{
|
|
Hash: h.Hash,
|
|
StatusMessages: h.Messages(),
|
|
DatasyncSender: h.DatasyncSender,
|
|
DatasyncAcks: h.DatasyncAcks,
|
|
DatasyncOffers: h.DatasyncOffers,
|
|
DatasyncRequests: h.DatasyncRequests,
|
|
}
|
|
}
|
|
|
|
type handleMessageResponse struct {
|
|
Hash []byte
|
|
Message *v1protocol.StatusMessage
|
|
DatasyncMessages []*v1protocol.StatusMessage
|
|
DatasyncSender *ecdsa.PublicKey
|
|
DatasyncAcks [][]byte
|
|
DatasyncOffers []DatasyncOffer
|
|
DatasyncRequests [][]byte
|
|
}
|
|
|
|
func (h *handleMessageResponse) Messages() []*v1protocol.StatusMessage {
|
|
if len(h.DatasyncMessages) > 0 {
|
|
return h.DatasyncMessages
|
|
}
|
|
return []*v1protocol.StatusMessage{h.Message}
|
|
}
|
|
|
|
func (s *MessageSender) handleMessage(wakuMessage *types.Message) (*handleMessageResponse, error) {
|
|
logger := s.logger.With(zap.String("site", "handleMessage"))
|
|
hlogger := logger.With(zap.String("hash", types.EncodeHex(wakuMessage.Hash)))
|
|
|
|
message := &v1protocol.StatusMessage{}
|
|
|
|
response := &handleMessageResponse{
|
|
Hash: wakuMessage.Hash,
|
|
Message: message,
|
|
DatasyncMessages: []*v1protocol.StatusMessage{},
|
|
DatasyncAcks: [][]byte{},
|
|
}
|
|
|
|
err := message.HandleTransportLayer(wakuMessage)
|
|
if err != nil {
|
|
hlogger.Error("failed to handle transport layer message", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
err = s.handleSegmentationLayerV2(message)
|
|
if err != nil {
|
|
hlogger.Debug("failed to handle segmentation layer message", zap.Error(err))
|
|
|
|
// Segments not completed yet, stop processing
|
|
if err == ErrMessageSegmentsIncomplete {
|
|
return nil, err
|
|
}
|
|
// Segments already completed, stop processing
|
|
if err == ErrMessageSegmentsAlreadyCompleted {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
err = s.handleEncryptionLayer(context.Background(), message)
|
|
if err != nil {
|
|
hlogger.Debug("failed to handle an encryption message", zap.Error(err))
|
|
|
|
// Hash ratchet with a group id not found yet, stop processing
|
|
if err == encryption.ErrHashRatchetGroupIDNotFound {
|
|
return response, err
|
|
}
|
|
}
|
|
|
|
if s.datasync != nil && s.datasyncEnabled {
|
|
err := s.unwrapDatasyncMessage(message, response)
|
|
if err != nil {
|
|
hlogger.Debug("failed to handle datasync message", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
for _, msg := range response.Messages() {
|
|
err := msg.HandleApplicationLayer()
|
|
if err != nil {
|
|
hlogger.Error("failed to handle application metadata layer message", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// fetchDecryptionKey returns the private key associated with this public key, and returns true if it's an ephemeral key
|
|
func (s *MessageSender) fetchDecryptionKey(destination *ecdsa.PublicKey) (*ecdsa.PrivateKey, bool) {
|
|
destinationID := types.EncodeHex(crypto.FromECDSAPub(destination))
|
|
|
|
s.ephemeralKeysMutex.Lock()
|
|
decryptionKey, ok := s.ephemeralKeys[destinationID]
|
|
s.ephemeralKeysMutex.Unlock()
|
|
|
|
// the key is not there, fallback on identity
|
|
if !ok {
|
|
return s.identity, false
|
|
}
|
|
return decryptionKey, true
|
|
}
|
|
|
|
func (s *MessageSender) handleEncryptionLayer(ctx context.Context, message *v1protocol.StatusMessage) error {
|
|
logger := s.logger.With(zap.String("site", "handleEncryptionLayer"))
|
|
publicKey := message.SigPubKey()
|
|
|
|
// if it's an ephemeral key, we don't negotiate a topic
|
|
decryptionKey, skipNegotiation := s.fetchDecryptionKey(message.TransportLayer.Dst)
|
|
|
|
err := message.HandleEncryptionLayer(decryptionKey, publicKey, s.protocol, skipNegotiation)
|
|
|
|
// if it's an ephemeral key, we don't have to handle a device not found error
|
|
if err == encryption.ErrDeviceNotFound && !skipNegotiation {
|
|
if err := s.handleErrDeviceNotFound(ctx, publicKey); err != nil {
|
|
logger.Error("failed to handle ErrDeviceNotFound", zap.Error(err))
|
|
}
|
|
}
|
|
if err != nil {
|
|
logger.Error("failed to handle an encrypted message", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *MessageSender) handleErrDeviceNotFound(ctx context.Context, publicKey *ecdsa.PublicKey) error {
|
|
now := time.Now().Unix()
|
|
advertise, err := s.protocol.ShouldAdvertiseBundle(publicKey, now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !advertise {
|
|
return nil
|
|
}
|
|
|
|
messageSpec, err := s.protocol.BuildBundleAdvertiseMessage(s.identity, publicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
|
defer cancel()
|
|
// We don't pass an array of messageIDs as no action needs to be taken
|
|
// when sending a bundle
|
|
_, _, err = s.sendMessageSpec(ctx, publicKey, messageSpec, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.protocol.ConfirmBundleAdvertisement(publicKey, now)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *MessageSender) wrapMessageV1(rawMessage *RawMessage) ([]byte, error) {
|
|
wrappedMessage, err := v1protocol.WrapMessageV1(rawMessage.Payload, rawMessage.MessageType, rawMessage.Sender)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap message")
|
|
}
|
|
return wrappedMessage, nil
|
|
}
|
|
|
|
func (s *MessageSender) addToDataSync(publicKey *ecdsa.PublicKey, message []byte) ([]byte, error) {
|
|
groupID := datasync.ToOneToOneGroupID(&s.identity.PublicKey, publicKey)
|
|
peerID := datasyncpeer.PublicKeyToPeerID(*publicKey)
|
|
exist, err := s.datasync.IsPeerInGroup(groupID, peerID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to check if peer is in group")
|
|
}
|
|
if !exist {
|
|
if err := s.datasync.AddPeer(groupID, peerID); err != nil {
|
|
return nil, errors.Wrap(err, "failed to add peer")
|
|
}
|
|
}
|
|
id, err := s.datasync.AppendMessage(groupID, message)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to append message to datasync")
|
|
}
|
|
|
|
return id[:], nil
|
|
}
|
|
|
|
// sendPrivateRawMessage sends a message not wrapped in an encryption layer
|
|
func (s *MessageSender) sendPrivateRawMessage(ctx context.Context, rawMessage *RawMessage, publicKey *ecdsa.PublicKey, payload []byte) ([][]byte, []*types.NewMessage, error) {
|
|
newMessage := &types.NewMessage{
|
|
TTL: whisperTTL,
|
|
Payload: payload,
|
|
PowTarget: calculatePoW(payload),
|
|
PowTime: whisperPoWTime,
|
|
PubsubTopic: rawMessage.PubsubTopic,
|
|
}
|
|
|
|
newMessages, err := s.segmentMessage(newMessage)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hashes := make([][]byte, 0, len(newMessages))
|
|
var hash []byte
|
|
for _, newMessage := range newMessages {
|
|
if rawMessage.SendOnPersonalTopic {
|
|
hash, err = s.transport.SendPrivateOnPersonalTopic(ctx, newMessage, publicKey)
|
|
} else {
|
|
hash, err = s.transport.SendPrivateWithPartitioned(ctx, newMessage, publicKey)
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
hashes = append(hashes, hash)
|
|
}
|
|
|
|
return hashes, newMessages, nil
|
|
}
|
|
|
|
// sendCommunityMessage sends a message not wrapped in an encryption layer
|
|
// to a community
|
|
func (s *MessageSender) dispatchCommunityMessage(ctx context.Context, publicKey *ecdsa.PublicKey, wrappedMessage []byte, pubsubTopic string, rekey bool, rawMessage *RawMessage) ([][]byte, []*types.NewMessage, error) {
|
|
payload := wrappedMessage
|
|
if rekey && len(rawMessage.HashRatchetGroupID) != 0 {
|
|
|
|
var ratchet *encryption.HashRatchetKeyCompatibility
|
|
var err error
|
|
// We have just rekeyed, pull the latest
|
|
if RekeyCompatibility {
|
|
ratchet, err = s.protocol.GetCurrentKeyForGroup(rawMessage.HashRatchetGroupID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
}
|
|
keyID, err := ratchet.GetKeyID()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
s.logger.Debug("adding key id to message", zap.String("keyid", types.Bytes2Hex(keyID)))
|
|
// We send the message over the community topic
|
|
spec, err := s.protocol.BuildHashRatchetReKeyGroupMessage(s.identity, rawMessage.Recipients, rawMessage.HashRatchetGroupID, wrappedMessage, ratchet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
payload, err = proto.Marshal(spec.Message)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
newMessage := &types.NewMessage{
|
|
TTL: whisperTTL,
|
|
Payload: payload,
|
|
PowTarget: calculatePoW(payload),
|
|
PowTime: whisperPoWTime,
|
|
PubsubTopic: pubsubTopic,
|
|
}
|
|
|
|
newMessages, err := s.segmentMessage(newMessage)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hashes := make([][]byte, 0, len(newMessages))
|
|
for _, newMessage := range newMessages {
|
|
hash, err := s.transport.SendCommunityMessage(ctx, newMessage, publicKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
hashes = append(hashes, hash)
|
|
}
|
|
|
|
return hashes, newMessages, nil
|
|
}
|
|
|
|
func (s *MessageSender) SendMessageSpec(ctx context.Context, publicKey *ecdsa.PublicKey, messageSpec *encryption.ProtocolMessageSpec, messageIDs [][]byte) ([][]byte, []*types.NewMessage, error) {
|
|
return s.sendMessageSpec(ctx, publicKey, messageSpec, messageIDs)
|
|
}
|
|
|
|
// sendMessageSpec analyses the spec properties and selects a proper transport method.
|
|
func (s *MessageSender) sendMessageSpec(ctx context.Context, publicKey *ecdsa.PublicKey, messageSpec *encryption.ProtocolMessageSpec, messageIDs [][]byte) ([][]byte, []*types.NewMessage, error) {
|
|
logger := s.logger.With(zap.String("site", "sendMessageSpec"))
|
|
|
|
newMessage, err := MessageSpecToWhisper(messageSpec)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
newMessages, err := s.segmentMessage(newMessage)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
hashes := make([][]byte, 0, len(newMessages))
|
|
var hash []byte
|
|
for _, newMessage := range newMessages {
|
|
// The shared secret needs to be handle before we send a message
|
|
// otherwise the topic might not be set up before we receive a message
|
|
if messageSpec.SharedSecret != nil && s.handleSharedSecrets != nil {
|
|
err := s.handleSharedSecrets([]*sharedsecret.Secret{messageSpec.SharedSecret})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
}
|
|
// process shared secret
|
|
if messageSpec.AgreedSecret {
|
|
logger.Debug("sending using shared secret")
|
|
hash, err = s.transport.SendPrivateWithSharedSecret(ctx, newMessage, publicKey, messageSpec.SharedSecret.Key)
|
|
} else {
|
|
logger.Debug("sending partitioned topic")
|
|
hash, err = s.transport.SendPrivateWithPartitioned(ctx, newMessage, publicKey)
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
hashes = append(hashes, hash)
|
|
}
|
|
|
|
sentMessage := &SentMessage{
|
|
PublicKey: publicKey,
|
|
Spec: messageSpec,
|
|
MessageIDs: messageIDs,
|
|
}
|
|
|
|
s.notifyOnSentMessage(sentMessage)
|
|
|
|
return hashes, newMessages, nil
|
|
}
|
|
|
|
func (s *MessageSender) SubscribeToMessageEvents() <-chan *MessageEvent {
|
|
c := make(chan *MessageEvent, 100)
|
|
s.messageEventsSubscriptions = append(s.messageEventsSubscriptions, c)
|
|
return c
|
|
}
|
|
|
|
func (s *MessageSender) notifyOnSentMessage(sentMessage *SentMessage) {
|
|
event := &MessageEvent{
|
|
Type: MessageSent,
|
|
SentMessage: sentMessage,
|
|
}
|
|
|
|
s.messageEventsSubscriptionsMutex.Lock()
|
|
defer s.messageEventsSubscriptionsMutex.Unlock()
|
|
|
|
// Publish on channels, drop if buffer is full
|
|
for _, c := range s.messageEventsSubscriptions {
|
|
select {
|
|
case c <- event:
|
|
default:
|
|
s.logger.Warn("message events subscription channel full when publishing sent event, dropping message")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (s *MessageSender) notifyOnScheduledMessage(recipient *ecdsa.PublicKey, message *RawMessage) {
|
|
event := &MessageEvent{
|
|
Recipient: recipient,
|
|
Type: MessageScheduled,
|
|
RawMessage: message,
|
|
}
|
|
|
|
s.messageEventsSubscriptionsMutex.Lock()
|
|
defer s.messageEventsSubscriptionsMutex.Unlock()
|
|
|
|
// Publish on channels, drop if buffer is full
|
|
for _, c := range s.messageEventsSubscriptions {
|
|
select {
|
|
case c <- event:
|
|
default:
|
|
s.logger.Warn("message events subscription channel full when publishing scheduled event, dropping message")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *MessageSender) JoinPublic(id string) (*transport.Filter, error) {
|
|
return s.transport.JoinPublic(id)
|
|
}
|
|
|
|
func (s *MessageSender) getRandomEphemeralKey() *ecdsa.PrivateKey {
|
|
k := rand.Intn(len(s.ephemeralKeys)) //nolint: gosec
|
|
for _, key := range s.ephemeralKeys {
|
|
if k == 0 {
|
|
return key
|
|
}
|
|
k--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *MessageSender) GetEphemeralKey() (*ecdsa.PrivateKey, error) {
|
|
s.ephemeralKeysMutex.Lock()
|
|
if len(s.ephemeralKeys) >= maxMessageSenderEphemeralKeys {
|
|
s.ephemeralKeysMutex.Unlock()
|
|
return s.getRandomEphemeralKey(), nil
|
|
}
|
|
privateKey, err := crypto.GenerateKey()
|
|
if err != nil {
|
|
s.ephemeralKeysMutex.Unlock()
|
|
return nil, err
|
|
}
|
|
|
|
s.ephemeralKeys[types.EncodeHex(crypto.FromECDSAPub(&privateKey.PublicKey))] = privateKey
|
|
s.ephemeralKeysMutex.Unlock()
|
|
_, err = s.transport.LoadKeyFilters(privateKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return privateKey, nil
|
|
}
|
|
|
|
func MessageSpecToWhisper(spec *encryption.ProtocolMessageSpec) (*types.NewMessage, error) {
|
|
var newMessage *types.NewMessage
|
|
|
|
payload, err := proto.Marshal(spec.Message)
|
|
if err != nil {
|
|
return newMessage, err
|
|
}
|
|
|
|
newMessage = &types.NewMessage{
|
|
TTL: whisperTTL,
|
|
Payload: payload,
|
|
PowTarget: calculatePoW(payload),
|
|
PowTime: whisperPoWTime,
|
|
}
|
|
return newMessage, nil
|
|
}
|
|
|
|
// calculatePoW returns the PoWTarget to be used.
|
|
// We check the size and arbitrarily set it to a lower PoW if the packet is
|
|
// greater than 50KB. We do this as the defaultPoW is too high for clients to send
|
|
// large messages.
|
|
func calculatePoW(payload []byte) float64 {
|
|
if len(payload) > largeSizeInBytes {
|
|
return whisperLargeSizePoW
|
|
}
|
|
return whisperDefaultPoW
|
|
}
|
|
|
|
func (s *MessageSender) StopDatasync() {
|
|
if s.datasync != nil {
|
|
s.datasync.Stop()
|
|
}
|
|
}
|
|
|
|
// GetCurrentKeyForGroup returns the latest key timestampID belonging to a key group
|
|
func (s *MessageSender) GetCurrentKeyForGroup(groupID []byte) (*encryption.HashRatchetKeyCompatibility, error) {
|
|
return s.protocol.GetCurrentKeyForGroup(groupID)
|
|
}
|
|
|
|
// GetKeyIDsForGroup returns a slice of key IDs belonging to a given group ID
|
|
func (s *MessageSender) GetKeysForGroup(groupID []byte) ([]*encryption.HashRatchetKeyCompatibility, error) {
|
|
return s.protocol.GetKeysForGroup(groupID)
|
|
}
|
|
|
|
func (s *MessageSender) CleanupHashRatchetEncryptedMessages() error {
|
|
monthAgo := time.Now().AddDate(0, -1, 0).Unix()
|
|
|
|
err := s.persistence.DeleteHashRatchetMessagesOlderThan(monthAgo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|