Refactor mailserver tests (#970)
This commit is contained in:
parent
e9da21cf87
commit
06e64cdde2
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
|
@ -73,7 +74,8 @@ func setupTestServer(t *testing.T) *WMailServer {
|
|||
}
|
||||
|
||||
func archiveEnvelope(t *testing.T, sentTime time.Time, server *WMailServer) *whisper.Envelope {
|
||||
env := generateEnvelope(t, sentTime)
|
||||
env, err := generateEnvelope(sentTime)
|
||||
require.NoError(t, err)
|
||||
server.Archive(env)
|
||||
|
||||
return env
|
||||
|
@ -82,14 +84,15 @@ func archiveEnvelope(t *testing.T, sentTime time.Time, server *WMailServer) *whi
|
|||
func testPrune(t *testing.T, u time.Time, expected int, c *Cleaner, s *WMailServer) {
|
||||
upper := uint32(u.Unix())
|
||||
_, err := c.Prune(0, upper)
|
||||
assert(err == nil, "", t)
|
||||
require.NoError(t, err)
|
||||
|
||||
count := countMessages(t, s.db)
|
||||
assert(count == expected, fmt.Sprintf("expected %d message, got: %d", expected, count), t)
|
||||
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
|
||||
}
|
||||
|
||||
func testMessagesCount(t *testing.T, expected int, s *WMailServer) {
|
||||
count := countMessages(t, s.db)
|
||||
assert(count == expected, fmt.Sprintf("expected %d message, got: %d", expected, count), t)
|
||||
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
|
||||
}
|
||||
|
||||
func countMessages(t *testing.T, db *leveldb.DB) int {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package mailserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type limiter struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
timeout time.Duration
|
||||
db map[string]time.Time
|
||||
}
|
||||
|
||||
func newLimiter(timeout time.Duration) *limiter {
|
||||
return &limiter{
|
||||
timeout: timeout,
|
||||
db: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *limiter) add(id string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.db[id] = time.Now()
|
||||
}
|
||||
|
||||
func (l *limiter) isAllowed(id string) bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
|
||||
if lastRequestTime, ok := l.db[id]; ok {
|
||||
return lastRequestTime.Add(l.timeout).Before(time.Now())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *limiter) deleteExpired() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for id, lastRequestTime := range l.db {
|
||||
if lastRequestTime.Add(l.timeout).Before(now) {
|
||||
delete(l.db, id)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package mailserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAllowed(t *testing.T) {
|
||||
peerID := "peerID"
|
||||
testCases := []struct {
|
||||
t time.Duration
|
||||
shouldBeAllowed bool
|
||||
db func() map[string]time.Time
|
||||
errMsg string
|
||||
info string
|
||||
}{
|
||||
{
|
||||
t: 5 * time.Millisecond,
|
||||
shouldBeAllowed: true,
|
||||
db: func() map[string]time.Time {
|
||||
return make(map[string]time.Time)
|
||||
},
|
||||
errMsg: "Expected limiter not to allow with empty db",
|
||||
info: "Expecting limiter.isAllowed to not allow with an empty db",
|
||||
},
|
||||
{
|
||||
t: 5 * time.Millisecond,
|
||||
shouldBeAllowed: true,
|
||||
db: func() map[string]time.Time {
|
||||
db := make(map[string]time.Time)
|
||||
db[peerID] = time.Now().Add(time.Duration(-10) * time.Millisecond)
|
||||
return db
|
||||
},
|
||||
errMsg: "Expected limiter to allow with peer on its db",
|
||||
info: "Expecting limiter.isAllowed to allow with an expired peer on its db",
|
||||
},
|
||||
{
|
||||
t: 5 * time.Millisecond,
|
||||
shouldBeAllowed: false,
|
||||
db: func() map[string]time.Time {
|
||||
db := make(map[string]time.Time)
|
||||
db[peerID] = time.Now().Add(time.Duration(-1) * time.Millisecond)
|
||||
return db
|
||||
},
|
||||
errMsg: "Expected limiter to not allow with peer on its db",
|
||||
info: "Expecting limiter.isAllowed to not allow with a non expired peer on its db",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.info, func(*testing.T) {
|
||||
l := newLimiter(tc.t)
|
||||
l.db = tc.db()
|
||||
assert.Equal(t, tc.shouldBeAllowed, l.isAllowed(peerID), tc.errMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveExpiredRateLimits(t *testing.T) {
|
||||
peer := "peer"
|
||||
l := newLimiter(time.Duration(5) * time.Second)
|
||||
for i := 0; i < 10; i++ {
|
||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
||||
l.db[peerID] = time.Now().Add(time.Duration(i*(-2)) * time.Second)
|
||||
}
|
||||
|
||||
l.deleteExpired()
|
||||
assert.Equal(t, 3, len(l.db))
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
||||
_, ok := l.db[peerID]
|
||||
assert.True(t, ok, fmt.Sprintf("Non expired peer '%s' should exist, but it doesn't", peerID))
|
||||
}
|
||||
for i := 3; i < 10; i++ {
|
||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
||||
_, ok := l.db[peerID]
|
||||
assert.False(t, ok, fmt.Sprintf("Expired peer '%s' should not exist, but it does", peerID))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddingLimts(t *testing.T) {
|
||||
peerID := "peerAdding"
|
||||
l := newLimiter(time.Duration(5) * time.Second)
|
||||
pre := time.Now()
|
||||
l.add(peerID)
|
||||
post := time.Now()
|
||||
assert.True(t, l.db[peerID].After(pre))
|
||||
assert.True(t, l.db[peerID].Before(post))
|
||||
}
|
|
@ -18,8 +18,9 @@ package mailserver
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
@ -36,7 +37,12 @@ const (
|
|||
maxQueryRange = 24 * time.Hour
|
||||
)
|
||||
|
||||
// WMailServer whisper mailserver
|
||||
var (
|
||||
errDirectoryNotProvided = errors.New("data directory not provided")
|
||||
errPasswordNotProvided = errors.New("password is not specified")
|
||||
)
|
||||
|
||||
// WMailServer whisper mailserver.
|
||||
type WMailServer struct {
|
||||
db *leveldb.DB
|
||||
w *whisper.Whisper
|
||||
|
@ -46,14 +52,14 @@ type WMailServer struct {
|
|||
tick *ticker
|
||||
}
|
||||
|
||||
// DBKey key to be stored on db
|
||||
// DBKey key to be stored on db.
|
||||
type DBKey struct {
|
||||
timestamp uint32
|
||||
hash common.Hash
|
||||
raw []byte
|
||||
}
|
||||
|
||||
// NewDbKey creates a new DBKey with the given values
|
||||
// NewDbKey creates a new DBKey with the given values.
|
||||
func NewDbKey(t uint32, h common.Hash) *DBKey {
|
||||
const sz = common.HashLength + 4
|
||||
var k DBKey
|
||||
|
@ -65,16 +71,16 @@ func NewDbKey(t uint32, h common.Hash) *DBKey {
|
|||
return &k
|
||||
}
|
||||
|
||||
// Init initializes mailServer
|
||||
// Init initializes mailServer.
|
||||
func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) error {
|
||||
var err error
|
||||
|
||||
if len(config.DataDir) == 0 {
|
||||
return fmt.Errorf("data directory not provided")
|
||||
return errDirectoryNotProvided
|
||||
}
|
||||
|
||||
if len(config.Password) == 0 {
|
||||
return fmt.Errorf("password is not specified")
|
||||
return errPasswordNotProvided
|
||||
}
|
||||
|
||||
s.db, err = leveldb.OpenFile(config.DataDir, nil)
|
||||
|
@ -85,34 +91,50 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e
|
|||
s.w = shh
|
||||
s.pow = config.MinimumPoW
|
||||
|
||||
if err := s.setupWhisperIdentity(config); err != nil {
|
||||
return err
|
||||
}
|
||||
s.setupLimiter(time.Duration(config.MailServerRateLimit) * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupLimiter in case limit is bigger than 0 it will setup an automated
|
||||
// limit db cleanup.
|
||||
func (s *WMailServer) setupLimiter(rateLimit time.Duration) {
|
||||
limit := rateLimit * time.Second
|
||||
if limit > 0 {
|
||||
s.limit = newLimiter(limit)
|
||||
s.setupMailServerCleanup(limit)
|
||||
}
|
||||
}
|
||||
|
||||
// setupWhisperIdentity setup the whisper identity (symkey) for the current mail
|
||||
// server.
|
||||
func (s *WMailServer) setupWhisperIdentity(config *params.WhisperConfig) error {
|
||||
MailServerKeyID, err := s.w.AddSymKeyFromPassword(config.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create symmetric key: %s", err)
|
||||
}
|
||||
|
||||
s.key, err = s.w.GetSymKey(MailServerKeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save symmetric key: %s", err)
|
||||
}
|
||||
limit := time.Duration(config.MailServerRateLimit) * time.Second
|
||||
if limit > 0 {
|
||||
s.limit = newLimiter(limit)
|
||||
s.setupMailServerCleanup(limit)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupMailServerCleanup periodically runs an expired entries deleteion for
|
||||
// stored limits.
|
||||
func (s *WMailServer) setupMailServerCleanup(period time.Duration) {
|
||||
if period <= 0 {
|
||||
return
|
||||
}
|
||||
if s.tick == nil {
|
||||
s.tick = &ticker{}
|
||||
}
|
||||
go s.tick.run(period, s.limit.deleteExpired)
|
||||
}
|
||||
|
||||
// Close the mailserver and its associated db connection
|
||||
// Close the mailserver and its associated db connection.
|
||||
func (s *WMailServer) Close() {
|
||||
if s.db != nil {
|
||||
if err := s.db.Close(); err != nil {
|
||||
|
@ -124,41 +146,47 @@ func (s *WMailServer) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// Archive a whisper envelope
|
||||
// Archive a whisper envelope.
|
||||
func (s *WMailServer) Archive(env *whisper.Envelope) {
|
||||
key := NewDbKey(env.Expiry-env.TTL, env.Hash())
|
||||
rawEnvelope, err := rlp.EncodeToBytes(env)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
|
||||
} else {
|
||||
err = s.db.Put(key.raw, rawEnvelope, nil)
|
||||
if err != nil {
|
||||
if err = s.db.Put(key.raw, rawEnvelope, nil); err != nil {
|
||||
log.Error(fmt.Sprintf("Writing to DB failed: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeliverMail sends mail to specified whisper peer
|
||||
// DeliverMail sends mail to specified whisper peer.
|
||||
func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) {
|
||||
if peer == nil {
|
||||
log.Error("Whisper peer is nil")
|
||||
return
|
||||
}
|
||||
s.managePeerLimits(peer.ID())
|
||||
|
||||
if ok, lower, upper, bloom := s.validateRequest(peer.ID(), request); ok {
|
||||
s.processRequest(peer, lower, upper, bloom)
|
||||
}
|
||||
}
|
||||
|
||||
// managePeerLimits in case limit its been setup on the current server and limit
|
||||
// allows the query, it will store/update new query time for the current peer.
|
||||
func (s *WMailServer) managePeerLimits(peer []byte) {
|
||||
if s.limit != nil {
|
||||
peerID := string(peer.ID())
|
||||
peerID := string(peer)
|
||||
if !s.limit.isAllowed(peerID) {
|
||||
log.Info("peerID exceeded the number of requests per second")
|
||||
return
|
||||
}
|
||||
s.limit.add(peerID)
|
||||
}
|
||||
|
||||
ok, lower, upper, bloom := s.validateRequest(peer.ID(), request)
|
||||
if ok {
|
||||
s.processRequest(peer, lower, upper, bloom)
|
||||
}
|
||||
}
|
||||
|
||||
// processRequest processes the current request and re-sends all stored messages
|
||||
// accomplishing lower and upper limits.
|
||||
func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope {
|
||||
ret := make([]*whisper.Envelope, 0)
|
||||
var err error
|
||||
|
@ -197,6 +225,7 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bl
|
|||
return ret
|
||||
}
|
||||
|
||||
// validateRequest runs different validations on the current request.
|
||||
func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) (bool, uint32, uint32, []byte) {
|
||||
if s.pow > 0.0 && request.PoW() < s.pow {
|
||||
return false, 0, 0, nil
|
||||
|
@ -209,27 +238,14 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope)
|
|||
return false, 0, 0, nil
|
||||
}
|
||||
|
||||
src := crypto.FromECDSAPub(decrypted.Src)
|
||||
if len(src)-len(peerID) == 1 {
|
||||
src = src[1:]
|
||||
}
|
||||
|
||||
// if you want to check the signature, you can do it here. e.g.:
|
||||
// if !bytes.Equal(peerID, src) {
|
||||
if src == nil {
|
||||
log.Warn(fmt.Sprintf("Wrong signature of p2p request"))
|
||||
if err := s.checkMsgSignature(decrypted, peerID); err != nil {
|
||||
log.Warn(err.Error())
|
||||
return false, 0, 0, nil
|
||||
}
|
||||
|
||||
payloadSize := len(decrypted.Payload)
|
||||
bloom := decrypted.Payload[8 : 8+whisper.BloomFilterSize]
|
||||
if payloadSize < 8 {
|
||||
log.Warn(fmt.Sprintf("Undersized p2p request"))
|
||||
return false, 0, 0, nil
|
||||
} else if payloadSize == 8 {
|
||||
bloom = whisper.MakeFullNodeBloom()
|
||||
} else if payloadSize < 8+whisper.BloomFilterSize {
|
||||
log.Warn(fmt.Sprintf("Undersized bloom filter in p2p request"))
|
||||
bloom, err := s.bloomFromReceivedMessage(decrypted)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
return false, 0, 0, nil
|
||||
}
|
||||
|
||||
|
@ -246,46 +262,34 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope)
|
|||
return true, lower, upper, bloom
|
||||
}
|
||||
|
||||
type limiter struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
timeout time.Duration
|
||||
db map[string]time.Time
|
||||
// checkMsgSignature returns an error in case the message is not correcly signed
|
||||
func (s *WMailServer) checkMsgSignature(msg *whisper.ReceivedMessage, id []byte) error {
|
||||
src := crypto.FromECDSAPub(msg.Src)
|
||||
if len(src)-len(id) == 1 {
|
||||
src = src[1:]
|
||||
}
|
||||
|
||||
func newLimiter(timeout time.Duration) *limiter {
|
||||
return &limiter{
|
||||
timeout: timeout,
|
||||
db: make(map[string]time.Time),
|
||||
}
|
||||
// if you want to check the signature, you can do it here. e.g.:
|
||||
// if !bytes.Equal(peerID, src) {
|
||||
if src == nil {
|
||||
return errors.New("Wrong signature of p2p request")
|
||||
}
|
||||
|
||||
func (l *limiter) add(id string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.db[id] = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *limiter) isAllowed(id string) bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
// bloomFromReceivedMessage gor a given whisper.ReceivedMessage it extracts the
|
||||
// used bloom filter
|
||||
func (s *WMailServer) bloomFromReceivedMessage(msg *whisper.ReceivedMessage) ([]byte, error) {
|
||||
payloadSize := len(msg.Payload)
|
||||
|
||||
if lastRequestTime, ok := l.db[id]; ok {
|
||||
return lastRequestTime.Add(l.timeout).Before(time.Now())
|
||||
if payloadSize < 8 {
|
||||
return nil, errors.New("Undersized p2p request")
|
||||
} else if payloadSize == 8 {
|
||||
return whisper.MakeFullNodeBloom(), nil
|
||||
} else if payloadSize < 8+whisper.BloomFilterSize {
|
||||
return nil, errors.New("Undersized bloom filter in p2p request")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *limiter) deleteExpired() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for id, lastRequestTime := range l.db {
|
||||
if lastRequestTime.Add(l.timeout).Before(now) {
|
||||
delete(l.db, id)
|
||||
}
|
||||
}
|
||||
return msg.Payload[8 : 8+whisper.BloomFilterSize], nil
|
||||
}
|
||||
|
|
|
@ -20,21 +20,23 @@ import (
|
|||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const powRequirement = 0.00001
|
||||
const peerID = "peerID"
|
||||
|
||||
var keyID string
|
||||
var shh *whisper.Whisper
|
||||
var seed = time.Now().Unix()
|
||||
|
||||
type ServerTestParams struct {
|
||||
|
@ -45,83 +47,312 @@ type ServerTestParams struct {
|
|||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
func assert(statement bool, text string, t *testing.T) {
|
||||
if !statement {
|
||||
t.Fatal(text)
|
||||
func TestMailserverSuite(t *testing.T) {
|
||||
suite.Run(t, new(MailserverSuite))
|
||||
}
|
||||
|
||||
type MailserverSuite struct {
|
||||
suite.Suite
|
||||
server *WMailServer
|
||||
shh *whisper.Whisper
|
||||
config *params.WhisperConfig
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) SetupTest() {
|
||||
s.server = &WMailServer{}
|
||||
s.shh = whisper.New(&whisper.DefaultConfig)
|
||||
s.shh.RegisterServer(s.server)
|
||||
s.config = ¶ms.WhisperConfig{
|
||||
DataDir: "/tmp/",
|
||||
Password: "pwd",
|
||||
MailServerRateLimit: 5,
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBKey(t *testing.T) {
|
||||
func (s *MailserverSuite) TestInit() {
|
||||
testCases := []struct {
|
||||
config params.WhisperConfig
|
||||
expectedError error
|
||||
limiterActive bool
|
||||
info string
|
||||
}{
|
||||
{
|
||||
config: params.WhisperConfig{DataDir: ""},
|
||||
expectedError: errDirectoryNotProvided,
|
||||
limiterActive: false,
|
||||
info: "Initializing a mail server with a config with empty DataDir",
|
||||
},
|
||||
{
|
||||
config: params.WhisperConfig{DataDir: "/tmp/", Password: ""},
|
||||
expectedError: errPasswordNotProvided,
|
||||
limiterActive: false,
|
||||
info: "Initializing a mail server with a config with an empty password",
|
||||
},
|
||||
{
|
||||
config: params.WhisperConfig{DataDir: "/invalid-path", Password: "pwd"},
|
||||
expectedError: errors.New("open DB: mkdir /invalid-path: permission denied"),
|
||||
limiterActive: false,
|
||||
info: "Initializing a mail server with a config with an unexisting DataDir",
|
||||
},
|
||||
{
|
||||
config: *s.config,
|
||||
expectedError: nil,
|
||||
limiterActive: true,
|
||||
info: "Initializing a mail server with a config with correct config and active limiter",
|
||||
},
|
||||
{
|
||||
config: params.WhisperConfig{
|
||||
DataDir: "/tmp/",
|
||||
Password: "pwd",
|
||||
},
|
||||
expectedError: nil,
|
||||
limiterActive: false,
|
||||
info: "Initializing a mail server with a config with empty DataDir and inactive limiter",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.T().Run(tc.info, func(*testing.T) {
|
||||
s.server.limit = nil
|
||||
err := s.server.Init(s.shh, &tc.config)
|
||||
s.server.tick = nil
|
||||
s.server.Close()
|
||||
s.Equal(tc.expectedError, err)
|
||||
s.Equal(tc.limiterActive, (s.server.limit != nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) TestArchive() {
|
||||
err := s.server.Init(s.shh, s.config)
|
||||
s.server.tick = nil
|
||||
s.NoError(err)
|
||||
defer s.server.Close()
|
||||
|
||||
env, err := generateEnvelope(time.Now())
|
||||
s.NoError(err)
|
||||
rawEnvelope, err := rlp.EncodeToBytes(env)
|
||||
s.NoError(err)
|
||||
|
||||
s.server.Archive(env)
|
||||
key := NewDbKey(env.Expiry-env.TTL, env.Hash())
|
||||
archivedEnvelope, err := s.server.db.Get(key.raw, nil)
|
||||
s.NoError(err)
|
||||
|
||||
s.Equal(rawEnvelope, archivedEnvelope)
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) TestManageLimits() {
|
||||
s.server.limit = newLimiter(time.Duration(5) * time.Millisecond)
|
||||
s.server.managePeerLimits([]byte("peerID"))
|
||||
s.Equal(1, len(s.server.limit.db))
|
||||
firstSaved := s.server.limit.db["peerID"]
|
||||
|
||||
// second call when limit is not accomplished does not store a new limit
|
||||
s.server.managePeerLimits([]byte("peerID"))
|
||||
s.Equal(1, len(s.server.limit.db))
|
||||
s.Equal(firstSaved, s.server.limit.db["peerID"])
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) TestDBKey() {
|
||||
var h common.Hash
|
||||
i := uint32(time.Now().Unix())
|
||||
k := NewDbKey(i, h)
|
||||
assert(len(k.raw) == common.HashLength+4, "wrong DB key length", t)
|
||||
assert(byte(i%0x100) == k.raw[3], "raw representation should be big endian", t)
|
||||
assert(byte(i/0x1000000) == k.raw[0], "big endian expected", t)
|
||||
s.Equal(len(k.raw), common.HashLength+4, "wrong DB key length")
|
||||
s.Equal(byte(i%0x100), k.raw[3], "raw representation should be big endian")
|
||||
s.Equal(byte(i/0x1000000), k.raw[0], "big endian expected")
|
||||
}
|
||||
|
||||
func TestMailServer(t *testing.T) {
|
||||
func (s *MailserverSuite) TestMailServer() {
|
||||
var server WMailServer
|
||||
|
||||
setupServer(t, &server)
|
||||
s.setupServer(&server)
|
||||
defer server.Close()
|
||||
|
||||
env := generateEnvelope(t, time.Now())
|
||||
env, err := generateEnvelope(time.Now())
|
||||
s.NoError(err)
|
||||
|
||||
server.Archive(env)
|
||||
deliverTest(t, &server, env)
|
||||
testCases := []struct {
|
||||
params *ServerTestParams
|
||||
emptyLow bool
|
||||
lowModifier int32
|
||||
uppModifier int32
|
||||
topic byte
|
||||
expect bool
|
||||
shouldFail bool
|
||||
info string
|
||||
}{
|
||||
{
|
||||
params: s.defaultServerParams(env),
|
||||
lowModifier: 0,
|
||||
uppModifier: 0,
|
||||
expect: true,
|
||||
shouldFail: false,
|
||||
info: "Processing a request where from and to are equals to an existing register, should provide results",
|
||||
},
|
||||
{
|
||||
params: s.defaultServerParams(env),
|
||||
lowModifier: 1,
|
||||
uppModifier: 1,
|
||||
expect: false,
|
||||
shouldFail: false,
|
||||
info: "Processing a request where from and to are great than any existing register, should not provide results",
|
||||
},
|
||||
{
|
||||
params: s.defaultServerParams(env),
|
||||
lowModifier: 0,
|
||||
uppModifier: 1,
|
||||
topic: 0xFF,
|
||||
expect: false,
|
||||
shouldFail: false,
|
||||
info: "Processing a request where to is grat than any existing register and with a specific topic, should not provide results",
|
||||
},
|
||||
{
|
||||
params: s.defaultServerParams(env),
|
||||
emptyLow: true,
|
||||
lowModifier: 4,
|
||||
uppModifier: -1,
|
||||
shouldFail: true,
|
||||
info: "Processing a request where to is lower than from should fail",
|
||||
},
|
||||
{
|
||||
params: s.defaultServerParams(env),
|
||||
emptyLow: true,
|
||||
lowModifier: 0,
|
||||
uppModifier: 24,
|
||||
shouldFail: true,
|
||||
info: "Processing a request where difference between from and to is > 24 should fail",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.T().Run(tc.info, func(*testing.T) {
|
||||
if tc.lowModifier != 0 {
|
||||
tc.params.low = tc.params.birth + uint32(tc.lowModifier)
|
||||
}
|
||||
if tc.uppModifier != 0 {
|
||||
tc.params.upp = tc.params.birth + uint32(tc.uppModifier)
|
||||
}
|
||||
if tc.emptyLow {
|
||||
tc.params.low = 0
|
||||
}
|
||||
if tc.topic == 0xFF {
|
||||
tc.params.topic[0] = tc.topic
|
||||
}
|
||||
|
||||
func TestRateLimits(t *testing.T) {
|
||||
l := newLimiter(time.Duration(5 * time.Millisecond))
|
||||
assert(l.isAllowed(peerID), "Expected limiter not to allow with empty db", t)
|
||||
|
||||
l.db[peerID] = time.Now().Add(time.Duration(-10 * time.Millisecond))
|
||||
assert(l.isAllowed(peerID), "Expected limiter to allow with peer on its db", t)
|
||||
|
||||
l.db[peerID] = time.Now().Add(time.Duration(-1 * time.Millisecond))
|
||||
assert(!l.isAllowed(peerID), "Expected limiter to not allow with peer on its db", t)
|
||||
request := s.createRequest(tc.params)
|
||||
src := crypto.FromECDSAPub(&tc.params.key.PublicKey)
|
||||
ok, lower, upper, bloom := server.validateRequest(src, request)
|
||||
if tc.shouldFail {
|
||||
if ok {
|
||||
s.T().Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
s.T().Fatalf("request validation failed, seed: %d.", seed)
|
||||
}
|
||||
if lower != tc.params.low {
|
||||
s.T().Fatalf("request validation failed (lower bound), seed: %d.", seed)
|
||||
}
|
||||
if upper != tc.params.upp {
|
||||
s.T().Fatalf("request validation failed (upper bound), seed: %d.", seed)
|
||||
}
|
||||
expectedBloom := whisper.TopicToBloom(tc.params.topic)
|
||||
if !bytes.Equal(bloom, expectedBloom) {
|
||||
s.T().Fatalf("request validation failed (topic), seed: %d.", seed)
|
||||
}
|
||||
|
||||
func TestRemoveExpiredRateLimits(t *testing.T) {
|
||||
l := newLimiter(time.Duration(10) * time.Second)
|
||||
l.db[peerID] = time.Now().Add(time.Duration(-10) * time.Second)
|
||||
l.db[peerID+"A"] = time.Now().Add(time.Duration(10) * time.Second)
|
||||
l.deleteExpired()
|
||||
_, ok := l.db[peerID]
|
||||
assert(!ok, "Expired peer should not exist, but it does ", t)
|
||||
_, ok = l.db[peerID+"A"]
|
||||
assert(ok, "Non expired peer should exist, but it doesn't", t)
|
||||
var exist bool
|
||||
mail := server.processRequest(nil, tc.params.low, tc.params.upp, bloom)
|
||||
for _, msg := range mail {
|
||||
if msg.Hash() == env.Hash() {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func generateEnvelope(t *testing.T, now time.Time) *whisper.Envelope {
|
||||
h := crypto.Keccak256Hash([]byte("test sample data"))
|
||||
params := &whisper.MessageParams{
|
||||
KeySym: h[:],
|
||||
Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
||||
Payload: []byte("test payload"),
|
||||
PoW: powRequirement,
|
||||
WorkTime: 2,
|
||||
if exist != tc.expect {
|
||||
s.T().Fatalf("error: exist = %v, seed: %d.", exist, seed)
|
||||
}
|
||||
|
||||
msg, err := whisper.NewSentMessage(params)
|
||||
src[0]++
|
||||
ok, lower, upper, _ = server.validateRequest(src, request)
|
||||
if !ok {
|
||||
// request should be valid regardless of signature
|
||||
s.T().Fatalf("request validation false negative, seed: %d (lower: %d, upper: %d).", seed, lower, upper)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) TestBloomFromReceivedMessage() {
|
||||
testCases := []struct {
|
||||
msg whisper.ReceivedMessage
|
||||
expectedBloom []byte
|
||||
expectedErr error
|
||||
info string
|
||||
}{
|
||||
{
|
||||
msg: whisper.ReceivedMessage{},
|
||||
expectedBloom: []byte(nil),
|
||||
expectedErr: errors.New("Undersized p2p request"),
|
||||
info: "getting bloom filter for an empty whisper message should produce an error",
|
||||
},
|
||||
{
|
||||
msg: whisper.ReceivedMessage{Payload: []byte("hohohohoho")},
|
||||
expectedBloom: []byte(nil),
|
||||
expectedErr: errors.New("Undersized bloom filter in p2p request"),
|
||||
info: "getting bloom filter for a malformed whisper message should produce an error",
|
||||
},
|
||||
{
|
||||
msg: whisper.ReceivedMessage{Payload: []byte("12345678")},
|
||||
expectedBloom: whisper.MakeFullNodeBloom(),
|
||||
expectedErr: nil,
|
||||
info: "getting bloom filter for a valid whisper message should be successful",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.T().Run(tc.info, func(*testing.T) {
|
||||
bloom, err := s.server.bloomFromReceivedMessage(&tc.msg)
|
||||
s.Equal(tc.expectedErr, err)
|
||||
s.Equal(tc.expectedBloom, bloom)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) setupServer(server *WMailServer) {
|
||||
const password = "password_for_this_test"
|
||||
const dbPath = "whisper-server-test"
|
||||
|
||||
dir, err := ioutil.TempDir("", dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
||||
}
|
||||
env, err := msg.Wrap(params, now)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to wrap with seed %d: %s.", seed, err)
|
||||
}
|
||||
return env
|
||||
s.T().Fatal(err)
|
||||
}
|
||||
|
||||
func serverParams(t *testing.T, env *whisper.Envelope) *ServerTestParams {
|
||||
id, err := shh.NewKeyPair()
|
||||
s.shh = whisper.New(&whisper.DefaultConfig)
|
||||
s.shh.RegisterServer(server)
|
||||
|
||||
err = server.Init(s.shh, ¶ms.WhisperConfig{DataDir: dir, Password: password, MinimumPoW: powRequirement})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
||||
s.T().Fatal(err)
|
||||
}
|
||||
testPeerID, err := shh.GetPrivateKey(id)
|
||||
|
||||
keyID, err = s.shh.AddSymKeyFromPassword(password)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
|
||||
s.T().Fatalf("failed to create symmetric key for mail request: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MailserverSuite) defaultServerParams(env *whisper.Envelope) *ServerTestParams {
|
||||
id, err := s.shh.NewKeyPair()
|
||||
if err != nil {
|
||||
s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
||||
}
|
||||
testPeerID, err := s.shh.GetPrivateKey(id)
|
||||
if err != nil {
|
||||
s.T().Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
|
||||
}
|
||||
birth := env.Expiry - env.TTL
|
||||
|
||||
|
@ -133,86 +364,17 @@ func serverParams(t *testing.T, env *whisper.Envelope) *ServerTestParams {
|
|||
key: testPeerID,
|
||||
}
|
||||
}
|
||||
func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
|
||||
p := serverParams(t, env)
|
||||
singleRequest(t, server, env, p, true)
|
||||
|
||||
p.low = p.birth + 1
|
||||
p.upp = p.birth + 1
|
||||
singleRequest(t, server, env, p, false)
|
||||
|
||||
p.low = p.birth
|
||||
p.upp = p.birth + 1
|
||||
p.topic[0] = 0xFF
|
||||
singleRequest(t, server, env, p, false)
|
||||
|
||||
p.low = 0
|
||||
p.upp = p.birth - 1
|
||||
failRequest(t, server, p, "validation should fail due to negative query time range")
|
||||
|
||||
p.low = 0
|
||||
p.upp = p.birth + 24
|
||||
failRequest(t, server, p, "validation should fail due to query big time range")
|
||||
}
|
||||
|
||||
func failRequest(t *testing.T, server *WMailServer, p *ServerTestParams, err string) {
|
||||
request := createRequest(t, p)
|
||||
src := crypto.FromECDSAPub(&p.key.PublicKey)
|
||||
ok, _, _, _ := server.validateRequest(src, request)
|
||||
if ok {
|
||||
t.Fatalf(err)
|
||||
}
|
||||
}
|
||||
|
||||
func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *ServerTestParams, expect bool) {
|
||||
request := createRequest(t, p)
|
||||
src := crypto.FromECDSAPub(&p.key.PublicKey)
|
||||
ok, lower, upper, bloom := server.validateRequest(src, request)
|
||||
if !ok {
|
||||
t.Fatalf("request validation failed, seed: %d.", seed)
|
||||
}
|
||||
if lower != p.low {
|
||||
t.Fatalf("request validation failed (lower bound), seed: %d.", seed)
|
||||
}
|
||||
if upper != p.upp {
|
||||
t.Fatalf("request validation failed (upper bound), seed: %d.", seed)
|
||||
}
|
||||
expectedBloom := whisper.TopicToBloom(p.topic)
|
||||
if !bytes.Equal(bloom, expectedBloom) {
|
||||
t.Fatalf("request validation failed (topic), seed: %d.", seed)
|
||||
}
|
||||
|
||||
var exist bool
|
||||
mail := server.processRequest(nil, p.low, p.upp, bloom)
|
||||
for _, msg := range mail {
|
||||
if msg.Hash() == env.Hash() {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if exist != expect {
|
||||
t.Fatalf("error: exist = %v, seed: %d.", exist, seed)
|
||||
}
|
||||
|
||||
src[0]++
|
||||
ok, lower, upper, _ = server.validateRequest(src, request)
|
||||
if !ok {
|
||||
// request should be valid regardless of signature
|
||||
t.Fatalf("request validation false negative, seed: %d (lower: %d, upper: %d).", seed, lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
|
||||
func (s *MailserverSuite) createRequest(p *ServerTestParams) *whisper.Envelope {
|
||||
bloom := whisper.TopicToBloom(p.topic)
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(data, p.low)
|
||||
binary.BigEndian.PutUint32(data[4:], p.upp)
|
||||
data = append(data, bloom...)
|
||||
|
||||
key, err := shh.GetSymKey(keyID)
|
||||
key, err := s.shh.GetSymKey(keyID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
||||
s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
||||
}
|
||||
|
||||
params := &whisper.MessageParams{
|
||||
|
@ -226,34 +388,33 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
|
|||
|
||||
msg, err := whisper.NewSentMessage(params)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
||||
s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
||||
}
|
||||
env, err := msg.Wrap(params, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to wrap with seed %d: %s.", seed, err)
|
||||
s.T().Fatalf("failed to wrap with seed %d: %s.", seed, err)
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func setupServer(t *testing.T, server *WMailServer) {
|
||||
const password = "password_for_this_test"
|
||||
const dbPath = "whisper-server-test"
|
||||
func generateEnvelope(sentTime time.Time) (*whisper.Envelope, error) {
|
||||
h := crypto.Keccak256Hash([]byte("test sample data"))
|
||||
params := &whisper.MessageParams{
|
||||
KeySym: h[:],
|
||||
Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
||||
Payload: []byte("test payload"),
|
||||
PoW: powRequirement,
|
||||
WorkTime: 2,
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", dbPath)
|
||||
msg, err := whisper.NewSentMessage(params)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err)
|
||||
}
|
||||
|
||||
shh = whisper.New(&whisper.DefaultConfig)
|
||||
shh.RegisterServer(server)
|
||||
|
||||
err = server.Init(shh, ¶ms.WhisperConfig{DataDir: dir, Password: password, MinimumPoW: powRequirement})
|
||||
env, err := msg.Wrap(params, sentTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, fmt.Errorf("failed to wrap with seed %d: %s", seed, err)
|
||||
}
|
||||
|
||||
keyID, err = shh.AddSymKeyFromPassword(password)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create symmetric key for mail request: %s", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue