2018-04-11 15:41:51 +00:00
|
|
|
package shhext
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-04-26 05:56:19 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"encoding/binary"
|
2018-07-02 07:38:10 +00:00
|
|
|
"encoding/hex"
|
2018-04-26 05:56:19 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2018-04-11 15:41:51 +00:00
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2018-04-24 15:50:26 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2018-04-26 05:56:19 +00:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
2018-04-11 15:41:51 +00:00
|
|
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
|
|
|
)
|
|
|
|
|
2018-04-26 05:56:19 +00:00
|
|
|
const (
|
|
|
|
// defaultWorkTime is a work time reported in messages sent to MailServer nodes.
|
|
|
|
defaultWorkTime = 5
|
2018-06-15 15:12:31 +00:00
|
|
|
// defaultRequestTimeout is the default request timeout in seconds
|
|
|
|
defaultRequestTimeout = 10
|
2018-04-26 05:56:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrInvalidMailServerPeer is returned when it fails to parse enode from params.
|
|
|
|
ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value")
|
|
|
|
// ErrInvalidSymKeyID is returned when it fails to get a symmetric key.
|
|
|
|
ErrInvalidSymKeyID = errors.New("invalid symKeyID value")
|
2018-07-04 09:30:57 +00:00
|
|
|
// ErrInvalidPublicKey is returned when public key can't be extracted
|
|
|
|
// from MailServer's nodeID.
|
|
|
|
ErrInvalidPublicKey = errors.New("can't extract public key")
|
2018-04-26 05:56:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// -----
|
|
|
|
// PAYLOADS
|
|
|
|
// -----
|
|
|
|
|
|
|
|
// MessagesRequest is a payload send to a MailServer to get messages.
|
|
|
|
type MessagesRequest struct {
|
|
|
|
// MailServerPeer is MailServer's enode address.
|
|
|
|
MailServerPeer string `json:"mailServerPeer"`
|
|
|
|
|
|
|
|
// From is a lower bound of time range (optional).
|
|
|
|
// Default is 24 hours back from now.
|
|
|
|
From uint32 `json:"from"`
|
|
|
|
|
|
|
|
// To is a upper bound of time range (optional).
|
|
|
|
// Default is now.
|
|
|
|
To uint32 `json:"to"`
|
|
|
|
|
2018-07-02 07:38:10 +00:00
|
|
|
// Limit determines the number of messages sent by the mail server
|
|
|
|
// for the current paginated request
|
|
|
|
Limit uint32 `json:"limit"`
|
|
|
|
|
|
|
|
// Cursor is used as starting point for paginated requests
|
|
|
|
Cursor string `json:"cursor"`
|
|
|
|
|
2018-04-26 05:56:19 +00:00
|
|
|
// Topic is a regular Whisper topic.
|
|
|
|
Topic whisper.TopicType `json:"topic"`
|
|
|
|
|
|
|
|
// SymKeyID is an ID of a symmetric key to authenticate to MailServer.
|
|
|
|
// It's derived from MailServer password.
|
2018-07-04 09:30:57 +00:00
|
|
|
//
|
|
|
|
// It's also possible to authenticate request with MailServerPeer
|
|
|
|
// public key.
|
2018-04-26 05:56:19 +00:00
|
|
|
SymKeyID string `json:"symKeyID"`
|
2018-06-15 15:12:31 +00:00
|
|
|
|
|
|
|
// Timeout is the time to live of the request specified in seconds.
|
|
|
|
// Default is 10 seconds
|
|
|
|
Timeout time.Duration `json:"timeout"`
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-05-04 08:23:38 +00:00
|
|
|
func (r *MessagesRequest) setDefaults(now time.Time) {
|
2018-04-26 05:56:19 +00:00
|
|
|
// set From and To defaults
|
2018-05-10 14:47:54 +00:00
|
|
|
if r.To == 0 {
|
2018-05-04 08:23:38 +00:00
|
|
|
r.To = uint32(now.UTC().Unix())
|
2018-04-11 15:41:51 +00:00
|
|
|
}
|
2018-05-10 14:47:54 +00:00
|
|
|
|
|
|
|
if r.From == 0 {
|
|
|
|
oneDay := uint32(86400) // -24 hours
|
|
|
|
if r.To < oneDay {
|
|
|
|
r.From = 0
|
|
|
|
} else {
|
|
|
|
r.From = r.To - oneDay
|
|
|
|
}
|
|
|
|
}
|
2018-06-15 15:12:31 +00:00
|
|
|
|
|
|
|
if r.Timeout == 0 {
|
|
|
|
r.Timeout = defaultRequestTimeout
|
|
|
|
}
|
2018-04-11 15:41:51 +00:00
|
|
|
}
|
|
|
|
|
2018-04-26 05:56:19 +00:00
|
|
|
// -----
|
|
|
|
// PUBLIC API
|
|
|
|
// -----
|
|
|
|
|
2018-04-11 15:41:51 +00:00
|
|
|
// PublicAPI extends whisper public API.
|
|
|
|
type PublicAPI struct {
|
2018-04-26 05:56:19 +00:00
|
|
|
service *Service
|
2018-04-24 15:50:26 +00:00
|
|
|
publicAPI *whisper.PublicWhisperAPI
|
2018-04-26 05:56:19 +00:00
|
|
|
log log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPublicAPI returns instance of the public API.
|
|
|
|
func NewPublicAPI(s *Service) *PublicAPI {
|
|
|
|
return &PublicAPI{
|
|
|
|
service: s,
|
|
|
|
publicAPI: whisper.NewPublicWhisperAPI(s.w),
|
|
|
|
log: log.New("package", "status-go/services/sshext.PublicAPI"),
|
|
|
|
}
|
2018-04-11 15:41:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Post shamelessly copied from whisper codebase with slight modifications.
|
2018-04-24 15:50:26 +00:00
|
|
|
func (api *PublicAPI) Post(ctx context.Context, req whisper.NewMessage) (hash hexutil.Bytes, err error) {
|
|
|
|
hash, err = api.publicAPI.Post(ctx, req)
|
2018-04-11 15:41:51 +00:00
|
|
|
if err == nil {
|
2018-04-24 15:50:26 +00:00
|
|
|
var envHash common.Hash
|
|
|
|
copy(envHash[:], hash[:]) // slice can't be used as key
|
2018-04-26 05:56:19 +00:00
|
|
|
api.service.tracker.Add(envHash)
|
2018-04-11 15:41:51 +00:00
|
|
|
}
|
|
|
|
return hash, err
|
|
|
|
}
|
2018-04-26 05:56:19 +00:00
|
|
|
|
|
|
|
// RequestMessages sends a request for historic messages to a MailServer.
|
2018-06-15 15:12:31 +00:00
|
|
|
func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (hexutil.Bytes, error) {
|
2018-04-26 05:56:19 +00:00
|
|
|
api.log.Info("RequestMessages", "request", r)
|
|
|
|
shh := api.service.w
|
2018-05-04 08:23:38 +00:00
|
|
|
now := api.service.w.GetCurrentTime()
|
|
|
|
r.setDefaults(now)
|
2018-06-26 08:41:03 +00:00
|
|
|
|
|
|
|
if r.From > r.To {
|
|
|
|
return nil, fmt.Errorf("Query range is invalid: from > to (%d > %d)", r.From, r.To)
|
|
|
|
}
|
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
var err error
|
|
|
|
|
2018-04-26 05:56:19 +00:00
|
|
|
mailServerNode, err := discover.ParseNode(r.MailServerPeer)
|
|
|
|
if err != nil {
|
2018-06-15 15:12:31 +00:00
|
|
|
return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err)
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
var (
|
|
|
|
symKey []byte
|
|
|
|
publicKey *ecdsa.PublicKey
|
|
|
|
)
|
|
|
|
|
|
|
|
if r.SymKeyID != "" {
|
|
|
|
symKey, err = shh.GetSymKey(r.SymKeyID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
publicKey, err = mailServerNode.ID.Pubkey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%v: %v", ErrInvalidPublicKey, err)
|
|
|
|
}
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-07-04 09:30:57 +00:00
|
|
|
envelope, err := makeEnvelop(
|
|
|
|
makePayload(r),
|
|
|
|
symKey,
|
|
|
|
publicKey,
|
|
|
|
api.service.nodeID,
|
|
|
|
shh.MinPow(),
|
|
|
|
now,
|
|
|
|
)
|
2018-04-26 05:56:19 +00:00
|
|
|
if err != nil {
|
2018-06-15 15:12:31 +00:00
|
|
|
return nil, err
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
2018-06-15 15:12:31 +00:00
|
|
|
|
|
|
|
hash := envelope.Hash()
|
2018-04-26 05:56:19 +00:00
|
|
|
if err := shh.RequestHistoricMessages(mailServerNode.ID[:], envelope); err != nil {
|
2018-06-15 15:12:31 +00:00
|
|
|
return nil, err
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-06-15 15:12:31 +00:00
|
|
|
api.service.tracker.AddRequest(hash, time.After(r.Timeout*time.Second))
|
|
|
|
|
|
|
|
return hash[:], nil
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-04-20 11:26:54 +00:00
|
|
|
// GetNewFilterMessages is a prototype method with deduplication
|
|
|
|
func (api *PublicAPI) GetNewFilterMessages(filterID string) ([]*whisper.Message, error) {
|
|
|
|
msgs, err := api.publicAPI.GetFilterMessages(filterID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-02 12:14:08 +00:00
|
|
|
return api.service.deduplicator.Deduplicate(msgs), err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfirmMessagesProcessed is a method to confirm that messages was consumed by
|
|
|
|
// the client side.
|
|
|
|
func (api *PublicAPI) ConfirmMessagesProcessed(messages []*whisper.Message) error {
|
|
|
|
return api.service.deduplicator.AddMessages(messages)
|
2018-04-20 11:26:54 +00:00
|
|
|
}
|
|
|
|
|
2018-04-26 05:56:19 +00:00
|
|
|
// -----
|
|
|
|
// HELPER
|
|
|
|
// -----
|
|
|
|
|
|
|
|
// makeEnvelop makes an envelop for a historic messages request.
|
|
|
|
// Symmetric key is used to authenticate to MailServer.
|
|
|
|
// PK is the current node ID.
|
2018-07-04 09:30:57 +00:00
|
|
|
func makeEnvelop(
|
|
|
|
payload []byte,
|
|
|
|
symKey []byte,
|
|
|
|
publicKey *ecdsa.PublicKey,
|
|
|
|
nodeID *ecdsa.PrivateKey,
|
|
|
|
pow float64,
|
|
|
|
now time.Time,
|
|
|
|
) (*whisper.Envelope, error) {
|
2018-04-26 05:56:19 +00:00
|
|
|
params := whisper.MessageParams{
|
|
|
|
PoW: pow,
|
|
|
|
Payload: payload,
|
|
|
|
WorkTime: defaultWorkTime,
|
|
|
|
Src: nodeID,
|
|
|
|
}
|
2018-07-04 09:30:57 +00:00
|
|
|
// Either symKey or public key is required.
|
|
|
|
// This condition is verified in `message.Wrap()` method.
|
|
|
|
if len(symKey) > 0 {
|
|
|
|
params.KeySym = symKey
|
|
|
|
} else if publicKey != nil {
|
|
|
|
params.Dst = publicKey
|
|
|
|
}
|
2018-04-26 05:56:19 +00:00
|
|
|
message, err := whisper.NewSentMessage(¶ms)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-04 08:23:38 +00:00
|
|
|
return message.Wrap(¶ms, now)
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// makePayload makes a specific payload for MailServer to request historic messages.
|
|
|
|
func makePayload(r MessagesRequest) []byte {
|
2018-07-02 07:38:10 +00:00
|
|
|
// Payload format:
|
|
|
|
// 4 bytes for lower
|
|
|
|
// 4 bytes for upper
|
|
|
|
// 64 bytes for the bloom filter
|
|
|
|
// 4 bytes for limit
|
|
|
|
// 36 bytes for the cursor. optional.
|
|
|
|
data := make([]byte, 12+whisper.BloomFilterSize)
|
|
|
|
|
|
|
|
// from
|
2018-04-26 05:56:19 +00:00
|
|
|
binary.BigEndian.PutUint32(data, r.From)
|
2018-07-02 07:38:10 +00:00
|
|
|
// to
|
2018-04-26 05:56:19 +00:00
|
|
|
binary.BigEndian.PutUint32(data[4:], r.To)
|
2018-07-02 07:38:10 +00:00
|
|
|
// bloom
|
2018-04-26 05:56:19 +00:00
|
|
|
copy(data[8:], whisper.TopicToBloom(r.Topic))
|
2018-07-02 07:38:10 +00:00
|
|
|
// limit
|
|
|
|
binary.BigEndian.PutUint32(data[8+whisper.BloomFilterSize:], r.Limit)
|
|
|
|
|
|
|
|
// cursor is the key of an envelope in leveldb.
|
|
|
|
// it's 36 bytes. 4 bytes for the timestamp + 32 bytes for the envelope hash
|
|
|
|
expectedCursorSize := common.HashLength + 4
|
|
|
|
cursorBytes, err := hex.DecodeString(r.Cursor)
|
|
|
|
if err != nil || len(cursorBytes) != expectedCursorSize {
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(data, cursorBytes...)
|
2018-04-26 05:56:19 +00:00
|
|
|
}
|