+3
@@ -0,0 +1,3 @@
|
||||
# Waku Common
|
||||
|
||||
[See here](../README.md#common)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
func IsFullNode(bloom []byte) bool {
|
||||
if bloom == nil {
|
||||
return true
|
||||
}
|
||||
for _, b := range bloom {
|
||||
if b != 255 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BloomFilterMatch(filter, sample []byte) bool {
|
||||
if filter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 0; i < BloomFilterSize; i++ {
|
||||
f := filter[i]
|
||||
s := sample[i]
|
||||
if (f | s) != f {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func MakeFullNodeBloom() []byte {
|
||||
bloom := make([]byte, BloomFilterSize)
|
||||
for i := 0; i < BloomFilterSize; i++ {
|
||||
bloom[i] = 0xFF
|
||||
}
|
||||
return bloom
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Waku protocol parameters
|
||||
const (
|
||||
SizeMask = byte(3) // mask used to extract the size of payload size field from the flags
|
||||
signatureFlag = byte(4)
|
||||
|
||||
TopicLength = 4 // in bytes
|
||||
signatureLength = crypto.SignatureLength // in bytes
|
||||
AESKeyLength = 32 // in bytes
|
||||
aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize()
|
||||
KeyIDSize = 32 // in bytes
|
||||
BloomFilterSize = 64 // in bytes
|
||||
MaxTopicInterest = 10000
|
||||
flagsLength = 1
|
||||
|
||||
EnvelopeHeaderLength = 20
|
||||
|
||||
MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
|
||||
DefaultMaxMessageSize = uint32(1024 * 1024)
|
||||
DefaultMinimumPoW = 0.2
|
||||
|
||||
padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol
|
||||
|
||||
ExpirationCycle = time.Second
|
||||
TransmissionCycle = 300 * time.Millisecond
|
||||
|
||||
DefaultTTL = 50 // seconds
|
||||
DefaultSyncAllowance = 10 // seconds
|
||||
|
||||
MaxLimitInSyncMailRequest = 1000
|
||||
|
||||
EnvelopeTimeNotSynced uint = iota + 1
|
||||
EnvelopeOtherError
|
||||
|
||||
MaxLimitInMessagesRequest = 1000
|
||||
)
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Envelope represents a clear-text data packet to transmit through the Waku
|
||||
// network. Its contents may or may not be encrypted and signed.
|
||||
type Envelope struct {
|
||||
Expiry uint32
|
||||
TTL uint32
|
||||
Topic TopicType
|
||||
Data []byte
|
||||
Nonce uint64
|
||||
|
||||
pow float64 // Message-specific PoW as described in the Waku specification.
|
||||
|
||||
// the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom()
|
||||
hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
|
||||
bloom []byte
|
||||
}
|
||||
|
||||
// Size returns the size of envelope as it is sent (i.e. public fields only)
|
||||
func (e *Envelope) Size() int {
|
||||
return EnvelopeHeaderLength + len(e.Data)
|
||||
}
|
||||
|
||||
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
|
||||
func (e *Envelope) rlpWithoutNonce() []byte {
|
||||
res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data})
|
||||
return res
|
||||
}
|
||||
|
||||
// NewEnvelope wraps a Waku message with expiration and destination data
|
||||
// included into an envelope for network forwarding.
|
||||
func NewEnvelope(ttl uint32, topic TopicType, msg *SentMessage, now time.Time) *Envelope {
|
||||
env := Envelope{
|
||||
Expiry: uint32(now.Add(time.Second * time.Duration(ttl)).Unix()),
|
||||
TTL: ttl,
|
||||
Topic: topic,
|
||||
Data: msg.Raw,
|
||||
Nonce: 0,
|
||||
}
|
||||
|
||||
return &env
|
||||
}
|
||||
|
||||
// Seal closes the envelope by spending the requested amount of time as a proof
|
||||
// of work on hashing the data.
|
||||
func (e *Envelope) Seal(options *MessageParams) error {
|
||||
if options.PoW == 0 {
|
||||
// PoW is not required
|
||||
return nil
|
||||
}
|
||||
|
||||
var target, bestLeadingZeros int
|
||||
if options.PoW < 0 {
|
||||
// target is not set - the function should run for a period
|
||||
// of time specified in WorkTime param. Since we can predict
|
||||
// the execution time, we can also adjust Expiry.
|
||||
e.Expiry += options.WorkTime
|
||||
} else {
|
||||
target = e.powToFirstBit(options.PoW)
|
||||
}
|
||||
|
||||
rwn := e.rlpWithoutNonce()
|
||||
buf := make([]byte, len(rwn)+8)
|
||||
copy(buf, rwn)
|
||||
asAnInt := new(big.Int)
|
||||
|
||||
finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
|
||||
for nonce := uint64(0); time.Now().UnixNano() < finish; {
|
||||
for i := 0; i < 1024; i++ {
|
||||
binary.BigEndian.PutUint64(buf[len(rwn):], nonce)
|
||||
h := crypto.Keccak256(buf)
|
||||
asAnInt.SetBytes(h)
|
||||
leadingZeros := 256 - asAnInt.BitLen()
|
||||
if leadingZeros > bestLeadingZeros {
|
||||
e.Nonce, bestLeadingZeros = nonce, leadingZeros
|
||||
if target > 0 && bestLeadingZeros >= target {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
nonce++
|
||||
}
|
||||
}
|
||||
|
||||
if target > 0 && bestLeadingZeros < target {
|
||||
return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PoW computes (if necessary) and returns the proof of work target
|
||||
// of the envelope.
|
||||
func (e *Envelope) PoW() float64 {
|
||||
if e.pow == 0 {
|
||||
e.CalculatePoW(0)
|
||||
}
|
||||
return e.pow
|
||||
}
|
||||
|
||||
func (e *Envelope) CalculatePoW(diff uint32) {
|
||||
rwn := e.rlpWithoutNonce()
|
||||
buf := make([]byte, len(rwn)+8)
|
||||
copy(buf, rwn)
|
||||
binary.BigEndian.PutUint64(buf[len(rwn):], e.Nonce)
|
||||
powHash := new(big.Int).SetBytes(crypto.Keccak256(buf))
|
||||
leadingZeroes := 256 - powHash.BitLen()
|
||||
x := math.Pow(2, float64(leadingZeroes))
|
||||
x /= float64(len(rwn))
|
||||
x /= float64(e.TTL + diff)
|
||||
e.pow = x
|
||||
}
|
||||
|
||||
func (e *Envelope) powToFirstBit(pow float64) int {
|
||||
x := pow
|
||||
x *= float64(e.Size())
|
||||
x *= float64(e.TTL)
|
||||
bits := math.Log2(x)
|
||||
bits = math.Ceil(bits)
|
||||
res := int(bits)
|
||||
if res < 1 {
|
||||
res = 1
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
|
||||
func (e *Envelope) Hash() common.Hash {
|
||||
if (e.hash == common.Hash{}) {
|
||||
encoded, _ := rlp.EncodeToBytes(e)
|
||||
e.hash = crypto.Keccak256Hash(encoded)
|
||||
}
|
||||
return e.hash
|
||||
}
|
||||
|
||||
// DecodeRLP decodes an Envelope from an RLP data stream.
|
||||
func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
|
||||
raw, err := s.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The decoding of Envelope uses the struct fields but also needs
|
||||
// to compute the hash of the whole RLP-encoded envelope. This
|
||||
// type has the same structure as Envelope but is not an
|
||||
// rlp.Decoder (does not implement DecodeRLP function).
|
||||
// Only public members will be encoded.
|
||||
type rlpenv Envelope
|
||||
if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.hash = crypto.Keccak256Hash(raw)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
|
||||
func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
|
||||
message := &ReceivedMessage{Raw: e.Data}
|
||||
err := message.decryptAsymmetric(key)
|
||||
switch err {
|
||||
case nil:
|
||||
return message, nil
|
||||
case ecies.ErrInvalidPublicKey: // addressed to somebody else
|
||||
return nil, err
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
|
||||
func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
|
||||
msg = &ReceivedMessage{Raw: e.Data}
|
||||
err = msg.decryptSymmetric(key)
|
||||
if err != nil {
|
||||
msg = nil
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// Open tries to decrypt an envelope, and populates the message fields in case of success.
|
||||
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
|
||||
if watcher == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The API interface forbids filters doing both symmetric and asymmetric encryption.
|
||||
if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if watcher.expectsAsymmetricEncryption() {
|
||||
msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
|
||||
if msg != nil {
|
||||
msg.Dst = &watcher.KeyAsym.PublicKey
|
||||
}
|
||||
} else if watcher.expectsSymmetricEncryption() {
|
||||
msg, _ = e.OpenSymmetric(watcher.KeySym)
|
||||
if msg != nil {
|
||||
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
||||
}
|
||||
}
|
||||
|
||||
if msg != nil {
|
||||
ok := msg.ValidateAndParse()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
msg.Topic = e.Topic
|
||||
msg.PoW = e.PoW()
|
||||
msg.TTL = e.TTL
|
||||
msg.Sent = e.Expiry - e.TTL
|
||||
msg.EnvelopeHash = e.Hash()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most).
|
||||
func (e *Envelope) Bloom() []byte {
|
||||
if e.bloom == nil {
|
||||
e.bloom = e.Topic.ToBloom()
|
||||
}
|
||||
return e.bloom
|
||||
}
|
||||
|
||||
// EnvelopeError code and optional description of the error.
|
||||
type EnvelopeError struct {
|
||||
Hash common.Hash
|
||||
Code uint
|
||||
Description string
|
||||
}
|
||||
|
||||
// ErrorToEnvelopeError converts common golang error into EnvelopeError with a code.
|
||||
func ErrorToEnvelopeError(hash common.Hash, err error) EnvelopeError {
|
||||
code := EnvelopeOtherError
|
||||
switch err.(type) {
|
||||
case TimeSyncError:
|
||||
code = EnvelopeTimeNotSynced
|
||||
}
|
||||
return EnvelopeError{
|
||||
Hash: hash,
|
||||
Code: code,
|
||||
Description: err.Error(),
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package common
|
||||
|
||||
// TimeSyncError error for clock skew errors.
|
||||
type TimeSyncError error
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// EventType used to define known waku events.
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
// EventEnvelopeSent fires when envelope was sent to a peer.
|
||||
EventEnvelopeSent EventType = "envelope.sent"
|
||||
|
||||
// EventEnvelopeExpired fires when envelop expired
|
||||
EventEnvelopeExpired EventType = "envelope.expired"
|
||||
|
||||
// EventEnvelopeReceived is sent once envelope was received from a peer.
|
||||
// EventEnvelopeReceived must be sent to the feed even if envelope was previously in the cache.
|
||||
// And event, ideally, should contain information about peer that sent envelope to us.
|
||||
EventEnvelopeReceived EventType = "envelope.received"
|
||||
|
||||
// EventBatchAcknowledged is sent when batch of envelopes was acknowledged by a peer.
|
||||
EventBatchAcknowledged EventType = "batch.acknowledged"
|
||||
|
||||
// EventEnvelopeAvailable fires when envelop is available for filters
|
||||
EventEnvelopeAvailable EventType = "envelope.available"
|
||||
|
||||
// EventMailServerRequestSent fires when such request is sent.
|
||||
EventMailServerRequestSent EventType = "mailserver.request.sent"
|
||||
|
||||
// EventMailServerRequestCompleted fires after mailserver sends all the requested messages
|
||||
EventMailServerRequestCompleted EventType = "mailserver.request.completed"
|
||||
|
||||
// EventMailServerRequestExpired fires after mailserver the request TTL ends.
|
||||
// This event is independent and concurrent to EventMailServerRequestCompleted.
|
||||
// Request should be considered as expired only if expiry event was received first.
|
||||
EventMailServerRequestExpired EventType = "mailserver.request.expired"
|
||||
|
||||
// EventMailServerEnvelopeArchived fires after an envelope has been archived
|
||||
EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived"
|
||||
|
||||
// EventMailServerSyncFinished fires when the sync of messages is finished.
|
||||
EventMailServerSyncFinished EventType = "mailserver.sync.finished"
|
||||
)
|
||||
|
||||
// EnvelopeEvent represents an envelope event.
|
||||
type EnvelopeEvent struct {
|
||||
Event EventType
|
||||
Topic TopicType
|
||||
Hash common.Hash
|
||||
Batch common.Hash
|
||||
Peer enode.ID
|
||||
Data interface{}
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Filter represents a Waku message filter
|
||||
type Filter struct {
|
||||
Src *ecdsa.PublicKey // Sender of the message
|
||||
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
|
||||
KeySym []byte // Key associated with the Topic
|
||||
Topics [][]byte // Topics to filter messages with
|
||||
PoW float64 // Proof of work as described in the Waku spec
|
||||
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
|
||||
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
|
||||
id string // unique identifier
|
||||
|
||||
Messages MessageStore
|
||||
}
|
||||
|
||||
// Filters represents a collection of filters
|
||||
type Filters struct {
|
||||
watchers map[string]*Filter
|
||||
|
||||
topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic
|
||||
allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is
|
||||
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewFilters returns a newly created filter collection
|
||||
func NewFilters() *Filters {
|
||||
return &Filters{
|
||||
watchers: make(map[string]*Filter),
|
||||
topicMatcher: make(map[TopicType]map[*Filter]struct{}),
|
||||
allTopicsMatcher: make(map[*Filter]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Install will add a new filter to the filter collection
|
||||
func (fs *Filters) Install(watcher *Filter) (string, error) {
|
||||
if watcher.KeySym != nil && watcher.KeyAsym != nil {
|
||||
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
|
||||
}
|
||||
|
||||
id, err := GenerateRandomID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
if fs.watchers[id] != nil {
|
||||
return "", fmt.Errorf("failed to generate unique ID")
|
||||
}
|
||||
|
||||
if watcher.expectsSymmetricEncryption() {
|
||||
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
||||
}
|
||||
|
||||
watcher.id = id
|
||||
fs.watchers[id] = watcher
|
||||
fs.addTopicMatcher(watcher)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Uninstall will remove a filter whose id has been specified from
|
||||
// the filter collection
|
||||
func (fs *Filters) Uninstall(id string) bool {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
if fs.watchers[id] != nil {
|
||||
fs.removeFromTopicMatchers(fs.watchers[id])
|
||||
delete(fs.watchers, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (fs *Filters) AllTopics() []TopicType {
|
||||
var topics []TopicType
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
for t := range fs.topicMatcher {
|
||||
topics = append(topics, t)
|
||||
}
|
||||
|
||||
return topics
|
||||
}
|
||||
|
||||
// addTopicMatcher adds a filter to the topic matchers.
|
||||
// If the filter's Topics array is empty, it will be tried on every topic.
|
||||
// Otherwise, it will be tried on the topics specified.
|
||||
func (fs *Filters) addTopicMatcher(watcher *Filter) {
|
||||
if len(watcher.Topics) == 0 {
|
||||
fs.allTopicsMatcher[watcher] = struct{}{}
|
||||
} else {
|
||||
for _, t := range watcher.Topics {
|
||||
topic := BytesToTopic(t)
|
||||
if fs.topicMatcher[topic] == nil {
|
||||
fs.topicMatcher[topic] = make(map[*Filter]struct{})
|
||||
}
|
||||
fs.topicMatcher[topic][watcher] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeFromTopicMatchers removes a filter from the topic matchers
|
||||
func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
|
||||
delete(fs.allTopicsMatcher, watcher)
|
||||
for _, topic := range watcher.Topics {
|
||||
delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
|
||||
}
|
||||
}
|
||||
|
||||
// GetWatchersByTopic returns a slice containing the filters that
|
||||
// match a specific topic
|
||||
func (fs *Filters) GetWatchersByTopic(topic TopicType) []*Filter {
|
||||
res := make([]*Filter, 0, len(fs.allTopicsMatcher))
|
||||
for watcher := range fs.allTopicsMatcher {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
for watcher := range fs.topicMatcher[topic] {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Get returns a filter from the collection with a specific ID
|
||||
func (fs *Filters) Get(id string) *Filter {
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
return fs.watchers[id]
|
||||
}
|
||||
|
||||
func (fs *Filters) All() []*Filter {
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
var filters []*Filter
|
||||
for _, f := range fs.watchers {
|
||||
filters = append(filters, f)
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
// NotifyWatchers notifies any filter that has declared interest
|
||||
// for the envelope's topic.
|
||||
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) bool {
|
||||
var msg *ReceivedMessage
|
||||
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
|
||||
var matched bool
|
||||
|
||||
candidates := fs.GetWatchersByTopic(env.Topic)
|
||||
for _, watcher := range candidates {
|
||||
if p2pMessage && !watcher.AllowP2P {
|
||||
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id))
|
||||
continue
|
||||
}
|
||||
|
||||
var match bool
|
||||
if msg != nil {
|
||||
match = watcher.MatchMessage(msg)
|
||||
} else {
|
||||
match = watcher.MatchEnvelope(env)
|
||||
if match {
|
||||
msg = env.Open(watcher)
|
||||
if msg == nil {
|
||||
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
} else {
|
||||
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
}
|
||||
|
||||
if match && msg != nil {
|
||||
msg.P2P = p2pMessage
|
||||
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
|
||||
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
|
||||
watcher.Trigger(msg)
|
||||
}
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
func (f *Filter) expectsAsymmetricEncryption() bool {
|
||||
return f.KeyAsym != nil
|
||||
}
|
||||
|
||||
func (f *Filter) expectsSymmetricEncryption() bool {
|
||||
return f.KeySym != nil
|
||||
}
|
||||
|
||||
// Trigger adds a yet-unknown message to the filter's list of
|
||||
// received messages.
|
||||
func (f *Filter) Trigger(msg *ReceivedMessage) {
|
||||
err := f.Messages.Add(msg)
|
||||
if err != nil {
|
||||
log.Error("failed to add msg into the filters store", "hash", msg.EnvelopeHash, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve will return the list of all received messages associated
|
||||
// to a filter.
|
||||
func (f *Filter) Retrieve() []*ReceivedMessage {
|
||||
msgs, err := f.Messages.Pop()
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to retrieve messages from filter store", "error", err)
|
||||
return nil
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// MatchMessage checks if the filter matches an already decrypted
|
||||
// message (i.e. a Message that has already been handled by
|
||||
// MatchEnvelope when checked by a previous filter).
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
|
||||
if f.PoW > 0 && msg.PoW < f.PoW {
|
||||
return false
|
||||
}
|
||||
|
||||
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
|
||||
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
|
||||
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
|
||||
return f.SymKeyHash == msg.SymKeyHash
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchEnvelope checks if it's worth decrypting the message. If
|
||||
// it returns `true`, client code is expected to attempt decrypting
|
||||
// the message and subsequently call MatchMessage.
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
|
||||
return f.PoW <= 0 || envelope.pow >= f.PoW
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// IsPubKeyEqual checks that two public keys are equal
|
||||
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
|
||||
if !ValidatePublicKey(a) {
|
||||
return false
|
||||
} else if !ValidatePublicKey(b) {
|
||||
return false
|
||||
}
|
||||
// the curve is always the same, just compare the points
|
||||
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
|
||||
}
|
||||
|
||||
// ValidatePublicKey checks the format of the given public key.
|
||||
func ValidatePublicKey(k *ecdsa.PublicKey) bool {
|
||||
return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
|
||||
}
|
||||
|
||||
// BytesToUintLittleEndian converts the slice to 64-bit unsigned integer.
|
||||
func BytesToUintLittleEndian(b []byte) (res uint64) {
|
||||
mul := uint64(1)
|
||||
for i := 0; i < len(b); i++ {
|
||||
res += uint64(b[i]) * mul
|
||||
mul *= 256
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// BytesToUintBigEndian converts the slice to 64-bit unsigned integer.
|
||||
func BytesToUintBigEndian(b []byte) (res uint64) {
|
||||
for i := 0; i < len(b); i++ {
|
||||
res *= 256
|
||||
res += uint64(b[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// ContainsOnlyZeros checks if the data contain only zeros.
|
||||
func ContainsOnlyZeros(data []byte) bool {
|
||||
for _, b := range data {
|
||||
if b != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GenerateSecureRandomData generates random data where extra security is required.
|
||||
// The purpose of this function is to prevent some bugs in software or in hardware
|
||||
// from delivering not-very-random data. This is especially useful for AES nonce,
|
||||
// where true randomness does not really matter, but it is very important to have
|
||||
// a unique nonce for every message.
|
||||
func GenerateSecureRandomData(length int) ([]byte, error) {
|
||||
x := make([]byte, length)
|
||||
y := make([]byte, length)
|
||||
res := make([]byte, length)
|
||||
|
||||
_, err := crand.Read(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ValidateDataIntegrity(x, length) {
|
||||
return nil, errors.New("crypto/rand failed to generate secure random data")
|
||||
}
|
||||
_, err = mrand.Read(y) // nolint: gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ValidateDataIntegrity(y, length) {
|
||||
return nil, errors.New("math/rand failed to generate secure random data")
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
res[i] = x[i] ^ y[i]
|
||||
}
|
||||
if !ValidateDataIntegrity(res, length) {
|
||||
return nil, errors.New("failed to generate secure random data")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateRandomID generates a random string, which is then returned to be used as a key id
|
||||
func GenerateRandomID() (id string, err error) {
|
||||
buf, err := GenerateSecureRandomData(KeyIDSize)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ValidateDataIntegrity(buf, KeyIDSize) {
|
||||
return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
|
||||
}
|
||||
id = common.Bytes2Hex(buf)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// ValidateDataIntegrity returns false if the data have the wrong or contains all zeros,
|
||||
// which is the simplest and the most common bug.
|
||||
func ValidateDataIntegrity(k []byte, expectedSize int) bool {
|
||||
if len(k) != expectedSize {
|
||||
return false
|
||||
}
|
||||
if expectedSize > 3 && ContainsOnlyZeros(k) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// MessageParams specifies the exact way a message should be wrapped
|
||||
// into an Envelope.
|
||||
type MessageParams struct {
|
||||
TTL uint32
|
||||
Src *ecdsa.PrivateKey
|
||||
Dst *ecdsa.PublicKey
|
||||
KeySym []byte
|
||||
Topic TopicType
|
||||
WorkTime uint32
|
||||
PoW float64
|
||||
Payload []byte
|
||||
Padding []byte
|
||||
}
|
||||
|
||||
// SentMessage represents an end-user data packet to transmit through the
|
||||
// Waku protocol. These are wrapped into Envelopes that need not be
|
||||
// understood by intermediate nodes, just forwarded.
|
||||
type SentMessage struct {
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
// ReceivedMessage represents a data packet to be received through the
|
||||
// Waku protocol and successfully decrypted.
|
||||
type ReceivedMessage struct {
|
||||
Raw []byte
|
||||
|
||||
Payload []byte
|
||||
Padding []byte
|
||||
Signature []byte
|
||||
Salt []byte
|
||||
|
||||
PoW float64 // Proof of work as described in the Waku spec
|
||||
Sent uint32 // Time when the message was posted into the network
|
||||
TTL uint32 // Maximum time to live allowed for the message
|
||||
Src *ecdsa.PublicKey // Message recipient (identity used to decode the message)
|
||||
Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message)
|
||||
Topic TopicType
|
||||
|
||||
SymKeyHash common.Hash // The Keccak256Hash of the key
|
||||
EnvelopeHash common.Hash // Message envelope hash to act as a unique id
|
||||
|
||||
P2P bool // is set to true if this message was received from mail server.
|
||||
}
|
||||
|
||||
// MessagesRequest contains details of a request for historic messages.
|
||||
type MessagesRequest struct {
|
||||
// ID of the request. The current implementation requires ID to be 32-byte array,
|
||||
// however, it's not enforced for future implementation.
|
||||
ID []byte `json:"id"`
|
||||
|
||||
// From is a lower bound of time range.
|
||||
From uint32 `json:"from"`
|
||||
|
||||
// To is a upper bound of time range.
|
||||
To uint32 `json:"to"`
|
||||
|
||||
// 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 []byte `json:"cursor"`
|
||||
|
||||
// Bloom is a filter to match requested messages.
|
||||
Bloom []byte `json:"bloom"`
|
||||
|
||||
// Topics is a list of topics. A returned message should
|
||||
// belong to one of the topics from the list.
|
||||
Topics [][]byte `json:"topics"`
|
||||
}
|
||||
|
||||
func (r MessagesRequest) Validate() error {
|
||||
if len(r.ID) != common.HashLength {
|
||||
return errors.New("invalid 'ID', expected a 32-byte slice")
|
||||
}
|
||||
|
||||
if r.From > r.To {
|
||||
return errors.New("invalid 'From' value which is greater than To")
|
||||
}
|
||||
|
||||
if r.Limit > MaxLimitInMessagesRequest {
|
||||
return fmt.Errorf("invalid 'Limit' value, expected value lower than %d", MaxLimitInMessagesRequest)
|
||||
}
|
||||
|
||||
if len(r.Bloom) == 0 && len(r.Topics) == 0 {
|
||||
return errors.New("invalid 'Bloom' or 'Topics', one must be non-empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MessagesResponse sent as a response after processing batch of envelopes.
|
||||
type MessagesResponse struct {
|
||||
// Hash is a hash of all envelopes sent in the single batch.
|
||||
Hash common.Hash
|
||||
// Per envelope error.
|
||||
Errors []EnvelopeError
|
||||
}
|
||||
|
||||
func IsMessageSigned(flags byte) bool {
|
||||
return (flags & signatureFlag) != 0
|
||||
}
|
||||
|
||||
func (msg *ReceivedMessage) isSymmetricEncryption() bool {
|
||||
return msg.SymKeyHash != common.Hash{}
|
||||
}
|
||||
|
||||
func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
|
||||
return msg.Dst != nil
|
||||
}
|
||||
|
||||
// NewSentMessage creates and initializes a non-signed, non-encrypted Waku message.
|
||||
func NewSentMessage(params *MessageParams) (*SentMessage, error) {
|
||||
const payloadSizeFieldMaxSize = 4
|
||||
msg := SentMessage{}
|
||||
msg.Raw = make([]byte, 1,
|
||||
flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
|
||||
msg.Raw[0] = 0 // set all the flags to zero
|
||||
msg.addPayloadSizeField(params.Payload)
|
||||
msg.Raw = append(msg.Raw, params.Payload...)
|
||||
err := msg.appendPadding(params)
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
// addPayloadSizeField appends the auxiliary field containing the size of payload
|
||||
func (msg *SentMessage) addPayloadSizeField(payload []byte) {
|
||||
fieldSize := getSizeOfPayloadSizeField(payload)
|
||||
field := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(field, uint32(len(payload)))
|
||||
field = field[:fieldSize]
|
||||
msg.Raw = append(msg.Raw, field...)
|
||||
msg.Raw[0] |= byte(fieldSize)
|
||||
}
|
||||
|
||||
// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload
|
||||
func getSizeOfPayloadSizeField(payload []byte) int {
|
||||
s := 1
|
||||
for i := len(payload); i >= 256; i /= 256 {
|
||||
s++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// appendPadding appends the padding specified in params.
|
||||
// If no padding is provided in params, then random padding is generated.
|
||||
func (msg *SentMessage) appendPadding(params *MessageParams) error {
|
||||
if len(params.Padding) != 0 {
|
||||
// padding data was provided by the Dapp, just use it as is
|
||||
msg.Raw = append(msg.Raw, params.Padding...)
|
||||
return nil
|
||||
}
|
||||
|
||||
rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload)
|
||||
if params.Src != nil {
|
||||
rawSize += signatureLength
|
||||
}
|
||||
odd := rawSize % padSizeLimit
|
||||
paddingSize := padSizeLimit - odd
|
||||
pad := make([]byte, paddingSize)
|
||||
_, err := crand.Read(pad)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ValidateDataIntegrity(pad, paddingSize) {
|
||||
return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize))
|
||||
}
|
||||
msg.Raw = append(msg.Raw, pad...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sign calculates and sets the cryptographic signature for the message,
|
||||
// also setting the sign flag.
|
||||
func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
|
||||
if IsMessageSigned(msg.Raw[0]) {
|
||||
// this should not happen, but no reason to panic
|
||||
log.Error("failed to sign the message: already signed")
|
||||
return nil
|
||||
}
|
||||
|
||||
msg.Raw[0] |= signatureFlag // it is important to set this flag before signing
|
||||
hash := crypto.Keccak256(msg.Raw)
|
||||
signature, err := crypto.Sign(hash, key)
|
||||
if err != nil {
|
||||
msg.Raw[0] &= 0xFF ^ signatureFlag // clear the flag
|
||||
return err
|
||||
}
|
||||
msg.Raw = append(msg.Raw, signature...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptAsymmetric encrypts a message with a public key.
|
||||
func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
|
||||
if !ValidatePublicKey(key) {
|
||||
return errors.New("invalid public key provided for asymmetric encryption")
|
||||
}
|
||||
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
|
||||
if err == nil {
|
||||
msg.Raw = encrypted
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256.
|
||||
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
||||
func (msg *SentMessage) encryptSymmetric(key []byte) (err error) {
|
||||
if !ValidateDataIntegrity(key, AESKeyLength) {
|
||||
return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key)))
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
salt, err := GenerateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil)
|
||||
msg.Raw = append(encrypted, salt...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrap bundles the message into an Envelope to transmit over the network.
|
||||
func (msg *SentMessage) Wrap(options *MessageParams, now time.Time) (envelope *Envelope, err error) {
|
||||
if options.TTL == 0 {
|
||||
options.TTL = DefaultTTL
|
||||
}
|
||||
if options.Src != nil {
|
||||
if err = msg.sign(options.Src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.Dst != nil {
|
||||
err = msg.encryptAsymmetric(options.Dst)
|
||||
} else if options.KeySym != nil {
|
||||
err = msg.encryptSymmetric(options.KeySym)
|
||||
} else {
|
||||
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envelope = NewEnvelope(options.TTL, options.Topic, msg, now)
|
||||
if err = envelope.Seal(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
|
||||
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
||||
func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
|
||||
// symmetric messages are expected to contain the 12-byte nonce at the end of the payload
|
||||
if len(msg.Raw) < aesNonceLength {
|
||||
return errors.New("missing salt or invalid payload in symmetric message")
|
||||
}
|
||||
salt := msg.Raw[len(msg.Raw)-aesNonceLength:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Raw = decrypted
|
||||
msg.Salt = salt
|
||||
return nil
|
||||
}
|
||||
|
||||
// decryptAsymmetric decrypts an encrypted payload with a private key.
|
||||
func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
|
||||
decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil)
|
||||
if err == nil {
|
||||
msg.Raw = decrypted
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateAndParse checks the message validity and extracts the fields in case of success.
|
||||
func (msg *ReceivedMessage) ValidateAndParse() bool {
|
||||
end := len(msg.Raw)
|
||||
if end < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
if IsMessageSigned(msg.Raw[0]) {
|
||||
end -= signatureLength
|
||||
if end <= 1 {
|
||||
return false
|
||||
}
|
||||
msg.Signature = msg.Raw[end : end+signatureLength]
|
||||
msg.Src = msg.SigToPubKey()
|
||||
if msg.Src == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
beg := 1
|
||||
payloadSize := 0
|
||||
sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload
|
||||
if sizeOfPayloadSizeField != 0 {
|
||||
if end < beg+sizeOfPayloadSizeField {
|
||||
return false
|
||||
}
|
||||
payloadSize = int(BytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField]))
|
||||
beg += sizeOfPayloadSizeField
|
||||
if beg+payloadSize > end {
|
||||
return false
|
||||
}
|
||||
msg.Payload = msg.Raw[beg : beg+payloadSize]
|
||||
}
|
||||
|
||||
beg += payloadSize
|
||||
msg.Padding = msg.Raw[beg:end]
|
||||
return true
|
||||
}
|
||||
|
||||
// SigToPubKey returns the public key associated to the message's
|
||||
// signature.
|
||||
func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
|
||||
// in case of invalid signature
|
||||
defer func() { recover() }() // nolint: errcheck
|
||||
|
||||
pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
|
||||
if err != nil {
|
||||
log.Error("failed to recover public key from signature", "err", err)
|
||||
return nil
|
||||
}
|
||||
return pub
|
||||
}
|
||||
|
||||
// hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding.
|
||||
func (msg *ReceivedMessage) hash() []byte {
|
||||
if IsMessageSigned(msg.Raw[0]) {
|
||||
sz := len(msg.Raw) - signatureLength
|
||||
return crypto.Keccak256(msg.Raw[:sz])
|
||||
}
|
||||
return crypto.Keccak256(msg.Raw)
|
||||
}
|
||||
|
||||
// MessageStore defines interface for temporary message store.
|
||||
type MessageStore interface {
|
||||
Add(*ReceivedMessage) error
|
||||
Pop() ([]*ReceivedMessage, error)
|
||||
}
|
||||
|
||||
// NewMemoryMessageStore returns pointer to an instance of the MemoryMessageStore.
|
||||
func NewMemoryMessageStore() *MemoryMessageStore {
|
||||
return &MemoryMessageStore{
|
||||
messages: map[common.Hash]*ReceivedMessage{},
|
||||
}
|
||||
}
|
||||
|
||||
// MemoryMessageStore represents messages stored in a memory hash table.
|
||||
type MemoryMessageStore struct {
|
||||
mu sync.Mutex
|
||||
messages map[common.Hash]*ReceivedMessage
|
||||
}
|
||||
|
||||
// Add adds message to store.
|
||||
func (store *MemoryMessageStore) Add(msg *ReceivedMessage) error {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
if _, exist := store.messages[msg.EnvelopeHash]; !exist {
|
||||
store.messages[msg.EnvelopeHash] = msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pop returns all available messages and cleans the store.
|
||||
func (store *MemoryMessageStore) Pop() ([]*ReceivedMessage, error) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
all := make([]*ReceivedMessage, 0, len(store.messages))
|
||||
for hash, msg := range store.messages {
|
||||
delete(store.messages, hash)
|
||||
all = append(all, msg)
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
prom "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
EnvelopesReceivedCounter = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_envelopes_received_total",
|
||||
Help: "Number of envelopes received.",
|
||||
})
|
||||
EnvelopesValidatedCounter = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_envelopes_validated_total",
|
||||
Help: "Number of envelopes processed successfully.",
|
||||
})
|
||||
EnvelopesRejectedCounter = prom.NewCounterVec(prom.CounterOpts{
|
||||
Name: "waku_envelopes_rejected_total",
|
||||
Help: "Number of envelopes rejected.",
|
||||
}, []string{"reason"})
|
||||
EnvelopesCacheFailedCounter = prom.NewCounterVec(prom.CounterOpts{
|
||||
Name: "waku_envelopes_cache_failures_total",
|
||||
Help: "Number of envelopes which failed to be cached.",
|
||||
}, []string{"type"})
|
||||
EnvelopesCachedCounter = prom.NewCounterVec(prom.CounterOpts{
|
||||
Name: "waku_envelopes_cached_total",
|
||||
Help: "Number of envelopes cached.",
|
||||
}, []string{"cache"})
|
||||
EnvelopesSizeMeter = prom.NewHistogram(prom.HistogramOpts{
|
||||
Name: "waku_envelopes_size_bytes",
|
||||
Help: "Size of processed Waku envelopes in bytes.",
|
||||
Buckets: prom.ExponentialBuckets(256, 4, 10),
|
||||
})
|
||||
RateLimitsProcessed = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_rate_limits_processed_total",
|
||||
Help: "Number of packets Waku rate limiter processed.",
|
||||
})
|
||||
RateLimitsExceeded = prom.NewCounterVec(prom.CounterOpts{
|
||||
Name: "waku_rate_limits_exceeded_total",
|
||||
Help: "Number of times the Waku rate limits were exceeded",
|
||||
}, []string{"type"})
|
||||
BridgeSent = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_bridge_sent_total",
|
||||
Help: "Number of envelopes bridged from Waku",
|
||||
})
|
||||
BridgeReceivedSucceed = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_bridge_received_success_total",
|
||||
Help: "Number of envelopes bridged to Waku and successfully added",
|
||||
})
|
||||
BridgeReceivedFailed = prom.NewCounter(prom.CounterOpts{
|
||||
Name: "waku_bridge_received_failure_total",
|
||||
Help: "Number of envelopes bridged to Waku and failed to be added",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prom.MustRegister(EnvelopesReceivedCounter)
|
||||
prom.MustRegister(EnvelopesRejectedCounter)
|
||||
prom.MustRegister(EnvelopesCacheFailedCounter)
|
||||
prom.MustRegister(EnvelopesCachedCounter)
|
||||
prom.MustRegister(EnvelopesSizeMeter)
|
||||
prom.MustRegister(RateLimitsProcessed)
|
||||
prom.MustRegister(RateLimitsExceeded)
|
||||
prom.MustRegister(BridgeSent)
|
||||
prom.MustRegister(BridgeReceivedSucceed)
|
||||
prom.MustRegister(BridgeReceivedFailed)
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Peer represents a remote Waku client with which the local host waku instance exchanges data / messages.
|
||||
type Peer interface {
|
||||
// Start performs the handshake and initialize the broadcasting of messages
|
||||
Start() error
|
||||
Stop()
|
||||
// Run start the polling loop
|
||||
Run() error
|
||||
|
||||
// NotifyAboutPowRequirementChange notifies the peer that POW for the host has changed
|
||||
NotifyAboutPowRequirementChange(float64) error
|
||||
// NotifyAboutBloomFilterChange notifies the peer that bloom filter for the host has changed
|
||||
NotifyAboutBloomFilterChange([]byte) error
|
||||
// NotifyAboutTopicInterestChange notifies the peer that topics for the host have changed
|
||||
NotifyAboutTopicInterestChange([]TopicType) error
|
||||
|
||||
// SetPeerTrusted sets the value of trusted, meaning we will
|
||||
// allow p2p messages from them, which is necessary to interact
|
||||
// with mailservers.
|
||||
SetPeerTrusted(bool)
|
||||
// SetRWWriter sets the socket to read/write
|
||||
SetRWWriter(p2p.MsgReadWriter)
|
||||
|
||||
RequestHistoricMessages(*Envelope) error
|
||||
SendMessagesRequest(MessagesRequest) error
|
||||
SendHistoricMessageResponse([]byte) error
|
||||
SendP2PMessages([]*Envelope) error
|
||||
SendRawP2PDirect([]rlp.RawValue) error
|
||||
|
||||
SendBundle(bundle []*Envelope) (rst common.Hash, err error)
|
||||
|
||||
// Mark marks an envelope known to the peer so that it won't be sent back.
|
||||
Mark(*Envelope)
|
||||
// Marked checks if an envelope is already known to the remote peer.
|
||||
Marked(*Envelope) bool
|
||||
|
||||
ID() []byte
|
||||
IP() net.IP
|
||||
EnodeID() enode.ID
|
||||
|
||||
PoWRequirement() float64
|
||||
BloomFilter() []byte
|
||||
ConfirmationsEnabled() bool
|
||||
}
|
||||
|
||||
// WakuHost is the local instance of waku, which both interacts with remote clients
|
||||
// (peers) and local clients (through RPC API)
|
||||
type WakuHost interface {
|
||||
// HandlePeer handles the connection of a new peer
|
||||
HandlePeer(Peer, p2p.MsgReadWriter) error
|
||||
// MaxMessageSize returns the maximum accepted message size.
|
||||
MaxMessageSize() uint32
|
||||
// LightClientMode returns whether the host is running in light client mode
|
||||
LightClientMode() bool
|
||||
// Mailserver returns whether the host is running a mailserver
|
||||
Mailserver() bool
|
||||
// LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed
|
||||
LightClientModeConnectionRestricted() bool
|
||||
// ConfirmationsEnabled returns true if message confirmations are enabled.
|
||||
ConfirmationsEnabled() bool
|
||||
// PacketRateLimits returns the current rate limits for the host
|
||||
PacketRateLimits() RateLimits
|
||||
// BytesRateLimits returns the current rate limits for the host
|
||||
BytesRateLimits() RateLimits
|
||||
// MinPow returns the MinPow for the host
|
||||
MinPow() float64
|
||||
// BloomFilterMode returns whether the host is using bloom filter
|
||||
BloomFilterMode() bool
|
||||
// BloomFilter returns the bloom filter for the host
|
||||
BloomFilter() []byte
|
||||
//TopicInterest returns the topics for the host
|
||||
TopicInterest() []TopicType
|
||||
// IsEnvelopeCached checks if envelope with specific hash has already been received and cached.
|
||||
IsEnvelopeCached(common.Hash) bool
|
||||
// Envelopes returns all the envelopes queued
|
||||
Envelopes() []*Envelope
|
||||
SendEnvelopeEvent(EnvelopeEvent) int
|
||||
// OnNewEnvelopes handles newly received envelopes from a peer
|
||||
OnNewEnvelopes([]*Envelope, Peer) ([]EnvelopeError, error)
|
||||
// OnNewP2PEnvelopes handles envelopes received though the P2P
|
||||
// protocol (i.e from a mailserver in most cases)
|
||||
OnNewP2PEnvelopes([]*Envelope) error
|
||||
// OnMessagesResponse handles when the peer receive a message response
|
||||
// from a mailserver
|
||||
OnMessagesResponse(MessagesResponse, Peer) error
|
||||
// OnMessagesRequest handles when the peer receive a message request
|
||||
// this only works if the peer is a mailserver
|
||||
OnMessagesRequest(MessagesRequest, Peer) error
|
||||
// OnDeprecatedMessagesRequest handles when the peer receive a message request
|
||||
// using the *Envelope format. Currently the only production client (status-mobile)
|
||||
// is exclusively using this one.
|
||||
OnDeprecatedMessagesRequest(*Envelope, Peer) error
|
||||
|
||||
OnBatchAcknowledged(common.Hash, Peer) error
|
||||
OnP2PRequestCompleted([]byte, Peer) error
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/tsenart/tb"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
var errRateLimitExceeded = errors.New("rate limit has been exceeded")
|
||||
|
||||
type runLoop func(rw p2p.MsgReadWriter) error
|
||||
|
||||
// RateLimiterPeer interface represents a Peer that is capable of being rate limited
|
||||
type RateLimiterPeer interface {
|
||||
ID() []byte
|
||||
IP() net.IP
|
||||
}
|
||||
|
||||
// RateLimiterHandler interface represents handler functionality for a Rate Limiter in the cases of
|
||||
// exceeding a peer limit and exceeding an IP limit
|
||||
type RateLimiterHandler interface {
|
||||
ExceedPeerLimit() error
|
||||
ExceedIPLimit() error
|
||||
}
|
||||
|
||||
// MetricsRateLimiterHandler implements RateLimiterHandler, represents a handler for reporting rate limit Exceed data
|
||||
// to the metrics collection service (currently prometheus)
|
||||
type MetricsRateLimiterHandler struct{}
|
||||
|
||||
func (MetricsRateLimiterHandler) ExceedPeerLimit() error {
|
||||
RateLimitsExceeded.WithLabelValues("peer_id").Inc()
|
||||
return nil
|
||||
}
|
||||
func (MetricsRateLimiterHandler) ExceedIPLimit() error {
|
||||
RateLimitsExceeded.WithLabelValues("ip").Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RateLimits contains information about rate limit settings.
|
||||
// It's agnostic on what it's being rate limited on (bytes or number of packets currently)
|
||||
// It's exchanged with the status-update packet code
|
||||
type RateLimits struct {
|
||||
IPLimits uint64 // amount per second from a single IP (default 0, no limits)
|
||||
PeerIDLimits uint64 // amount per second from a single peer ID (default 0, no limits)
|
||||
TopicLimits uint64 // amount per second from a single topic (default 0, no limits)
|
||||
}
|
||||
|
||||
func (r RateLimits) IsZero() bool {
|
||||
return r == (RateLimits{})
|
||||
}
|
||||
|
||||
// DropPeerRateLimiterHandler implements RateLimiterHandler, represents a handler that introduces Tolerance to the
|
||||
// number of Peer connections before Limit Exceeded errors are returned.
|
||||
type DropPeerRateLimiterHandler struct {
|
||||
// Tolerance is a number by which a limit must be exceeded before a peer is dropped.
|
||||
Tolerance int64
|
||||
|
||||
peerLimitExceeds int64
|
||||
ipLimitExceeds int64
|
||||
}
|
||||
|
||||
func (h *DropPeerRateLimiterHandler) ExceedPeerLimit() error {
|
||||
h.peerLimitExceeds++
|
||||
if h.Tolerance > 0 && h.peerLimitExceeds >= h.Tolerance {
|
||||
return errRateLimitExceeded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *DropPeerRateLimiterHandler) ExceedIPLimit() error {
|
||||
h.ipLimitExceeds++
|
||||
if h.Tolerance > 0 && h.ipLimitExceeds >= h.Tolerance {
|
||||
return errRateLimitExceeded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeerRateLimiterConfig represents configurations for initialising a PeerRateLimiter
|
||||
type PeerRateLimiterConfig struct {
|
||||
PacketLimitPerSecIP int64
|
||||
PacketLimitPerSecPeerID int64
|
||||
BytesLimitPerSecIP int64
|
||||
BytesLimitPerSecPeerID int64
|
||||
WhitelistedIPs []string
|
||||
WhitelistedPeerIDs []enode.ID
|
||||
}
|
||||
|
||||
var defaultPeerRateLimiterConfig = PeerRateLimiterConfig{
|
||||
PacketLimitPerSecIP: 10,
|
||||
PacketLimitPerSecPeerID: 5,
|
||||
BytesLimitPerSecIP: 1048576, // 1MB
|
||||
BytesLimitPerSecPeerID: 1048576, // 1MB
|
||||
WhitelistedIPs: nil,
|
||||
WhitelistedPeerIDs: nil,
|
||||
}
|
||||
|
||||
// PeerRateLimiter represents a rate limiter that limits communication between Peers
|
||||
type PeerRateLimiter struct {
|
||||
packetThrottler *tb.Throttler
|
||||
bytesThrottler *tb.Throttler
|
||||
|
||||
PacketLimitPerSecIP int64
|
||||
PacketLimitPerSecPeerID int64
|
||||
|
||||
BytesLimitPerSecIP int64
|
||||
BytesLimitPerSecPeerID int64
|
||||
|
||||
whitelistedPeerIDs []enode.ID
|
||||
whitelistedIPs []string
|
||||
|
||||
handlers []RateLimiterHandler
|
||||
}
|
||||
|
||||
func NewPeerRateLimiter(cfg *PeerRateLimiterConfig, handlers ...RateLimiterHandler) *PeerRateLimiter {
|
||||
if cfg == nil {
|
||||
cfgCopy := defaultPeerRateLimiterConfig
|
||||
cfg = &cfgCopy
|
||||
}
|
||||
|
||||
return &PeerRateLimiter{
|
||||
packetThrottler: tb.NewThrottler(time.Millisecond * 100),
|
||||
bytesThrottler: tb.NewThrottler(time.Millisecond * 100),
|
||||
PacketLimitPerSecIP: cfg.PacketLimitPerSecIP,
|
||||
PacketLimitPerSecPeerID: cfg.PacketLimitPerSecPeerID,
|
||||
BytesLimitPerSecIP: cfg.BytesLimitPerSecIP,
|
||||
BytesLimitPerSecPeerID: cfg.BytesLimitPerSecPeerID,
|
||||
whitelistedPeerIDs: cfg.WhitelistedPeerIDs,
|
||||
whitelistedIPs: cfg.WhitelistedIPs,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PeerRateLimiter) Decorate(p RateLimiterPeer, rw p2p.MsgReadWriter, runLoop runLoop) error {
|
||||
errC := make(chan error, 1)
|
||||
|
||||
in, out := p2p.MsgPipe()
|
||||
defer func() {
|
||||
if err := in.Close(); err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if err := out.Close(); err != nil {
|
||||
errC <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// Read from the original reader and write to the message pipe.
|
||||
go func() {
|
||||
for {
|
||||
packet, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- fmt.Errorf("failed to read packet: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
RateLimitsProcessed.Inc()
|
||||
|
||||
var ip string
|
||||
if p != nil {
|
||||
// this relies on <nil> being the string representation of nil
|
||||
// as IP() might return a nil value
|
||||
ip = p.IP().String()
|
||||
}
|
||||
if halted := r.throttleIP(ip, packet.Size); halted {
|
||||
for _, h := range r.handlers {
|
||||
if err := h.ExceedIPLimit(); err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
|
||||
case errC <- fmt.Errorf("exceed rate limit by IP: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var peerID []byte
|
||||
if p != nil {
|
||||
peerID = p.ID()
|
||||
}
|
||||
if halted := r.throttlePeer(peerID, packet.Size); halted {
|
||||
for _, h := range r.handlers {
|
||||
if err := h.ExceedPeerLimit(); err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- fmt.Errorf("exceeded rate limit by peer: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := in.WriteMsg(packet); err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- fmt.Errorf("failed to write packet to pipe: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Read from the message pipe and write to the original writer.
|
||||
go func() {
|
||||
for {
|
||||
packet, err := in.ReadMsg()
|
||||
if err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- fmt.Errorf("failed to read packet from pipe: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := rw.WriteMsg(packet); err != nil {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- fmt.Errorf("failed to write packet: %v", err):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Don't block as otherwise we might leak go routines
|
||||
select {
|
||||
case errC <- runLoop(out):
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return <-errC
|
||||
}
|
||||
|
||||
// throttleIP throttles packets incoming from a given IP.
|
||||
func (r *PeerRateLimiter) throttleIP(ip string, size uint32) bool {
|
||||
if stringSliceContains(r.whitelistedIPs, ip) {
|
||||
return false
|
||||
}
|
||||
|
||||
var packetLimiterResponse bool
|
||||
var bytesLimiterResponse bool
|
||||
|
||||
if r.PacketLimitPerSecIP != 0 {
|
||||
packetLimiterResponse = r.packetThrottler.Halt(ip, 1, r.PacketLimitPerSecIP)
|
||||
}
|
||||
if r.BytesLimitPerSecIP != 0 {
|
||||
bytesLimiterResponse = r.bytesThrottler.Halt(ip, int64(size), r.BytesLimitPerSecIP)
|
||||
}
|
||||
|
||||
return packetLimiterResponse || bytesLimiterResponse
|
||||
}
|
||||
|
||||
// throttlePeer throttles packets incoming from a peer.
|
||||
func (r *PeerRateLimiter) throttlePeer(peerID []byte, size uint32) bool {
|
||||
var id enode.ID
|
||||
copy(id[:], peerID)
|
||||
if enodeIDSliceContains(r.whitelistedPeerIDs, id) {
|
||||
return false
|
||||
}
|
||||
|
||||
var packetLimiterResponse bool
|
||||
var bytesLimiterResponse bool
|
||||
|
||||
if r.PacketLimitPerSecPeerID != 0 {
|
||||
packetLimiterResponse = r.packetThrottler.Halt(id.String(), 1, r.PacketLimitPerSecPeerID)
|
||||
}
|
||||
|
||||
if r.BytesLimitPerSecPeerID != 0 {
|
||||
bytesLimiterResponse = r.bytesThrottler.Halt(id.String(), int64(size), r.BytesLimitPerSecPeerID)
|
||||
}
|
||||
|
||||
return packetLimiterResponse || bytesLimiterResponse
|
||||
}
|
||||
|
||||
func stringSliceContains(s []string, searched string) bool {
|
||||
for _, item := range s {
|
||||
if item == searched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func enodeIDSliceContains(s []enode.ID, searched enode.ID) bool {
|
||||
for _, item := range s {
|
||||
if bytes.Equal(item.Bytes(), searched.Bytes()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
type Measure struct {
|
||||
Timestamp int64
|
||||
Size uint64
|
||||
}
|
||||
|
||||
type StatsTracker struct {
|
||||
Uploads []Measure
|
||||
Downloads []Measure
|
||||
|
||||
statsMutex sync.Mutex
|
||||
}
|
||||
|
||||
const measurementPeriod = 15 * time.Second
|
||||
|
||||
func measure(input interface{}) (*Measure, error) {
|
||||
b, err := rlp.EncodeToBytes(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Measure{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Size: uint64(len(b)),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *StatsTracker) AddUpload(input interface{}) {
|
||||
go func(input interface{}) {
|
||||
m, err := measure(input)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
s.Uploads = append(s.Uploads, *m)
|
||||
}(input)
|
||||
}
|
||||
|
||||
func (s *StatsTracker) AddDownload(input interface{}) {
|
||||
go func(input interface{}) {
|
||||
m, err := measure(input)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
s.Downloads = append(s.Downloads, *m)
|
||||
}(input)
|
||||
}
|
||||
|
||||
func (s *StatsTracker) AddUploadBytes(size uint64) {
|
||||
go func(size uint64) {
|
||||
m := Measure{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Size: size,
|
||||
}
|
||||
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
s.Uploads = append(s.Uploads, m)
|
||||
}(size)
|
||||
}
|
||||
|
||||
func (s *StatsTracker) AddDownloadBytes(size uint64) {
|
||||
go func(size uint64) {
|
||||
m := Measure{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Size: size,
|
||||
}
|
||||
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
s.Downloads = append(s.Downloads, m)
|
||||
}(size)
|
||||
}
|
||||
|
||||
func calculateAverage(measures []Measure, minTime int64) (validMeasures []Measure, rate uint64) {
|
||||
for _, m := range measures {
|
||||
if m.Timestamp > minTime {
|
||||
// Only use recent measures
|
||||
validMeasures = append(validMeasures, m)
|
||||
rate += m.Size
|
||||
}
|
||||
}
|
||||
|
||||
rate /= (uint64(measurementPeriod) / uint64(1*time.Second))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StatsTracker) GetRatePerSecond() (uploadRate uint64, downloadRate uint64) {
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
minTime := time.Now().Add(-measurementPeriod).UnixNano()
|
||||
s.Uploads, uploadRate = calculateAverage(s.Uploads, minTime)
|
||||
s.Downloads, downloadRate = calculateAverage(s.Downloads, minTime)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StatsTracker) GetStats() types.StatsSummary {
|
||||
uploadRate, downloadRate := s.GetRatePerSecond()
|
||||
summary := types.StatsSummary{
|
||||
UploadRate: uploadRate,
|
||||
DownloadRate: downloadRate,
|
||||
}
|
||||
return summary
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 The Waku Library Authors.
|
||||
//
|
||||
// The Waku library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Waku library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// This software uses the go-ethereum library, which is licensed
|
||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// TopicType represents a cryptographically secure, probabilistic partial
|
||||
// classifications of a message, determined as the first (leftmost) 4 bytes of the
|
||||
// SHA3 hash of some arbitrary data given by the original author of the message.
|
||||
type TopicType [TopicLength]byte
|
||||
|
||||
// BytesToTopic converts from the byte array representation of a topic
|
||||
// into the TopicType type.
|
||||
func BytesToTopic(b []byte) (t TopicType) {
|
||||
sz := TopicLength
|
||||
if x := len(b); x < TopicLength {
|
||||
sz = x
|
||||
}
|
||||
for i := 0; i < sz; i++ {
|
||||
t[i] = b[i]
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// String converts a topic byte array to a string representation.
|
||||
func (t *TopicType) String() string {
|
||||
return hexutil.Encode(t[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of t.
|
||||
func (t TopicType) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(t[:]).MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText parses a hex representation to a topic.
|
||||
func (t *TopicType) UnmarshalText(input []byte) error {
|
||||
return hexutil.UnmarshalFixedText("Topic", input, t[:])
|
||||
}
|
||||
|
||||
// ToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
|
||||
func (t TopicType) ToBloom() []byte {
|
||||
b := make([]byte, BloomFilterSize)
|
||||
var index [3]int
|
||||
for j := 0; j < 3; j++ {
|
||||
index[j] = int(t[j])
|
||||
if (t[3] & (1 << uint(j))) != 0 {
|
||||
index[j] += 256
|
||||
}
|
||||
}
|
||||
|
||||
for j := 0; j < 3; j++ {
|
||||
byteIndex := index[j] / 8
|
||||
bitIndex := index[j] % 8
|
||||
b[byteIndex] = 1 << uint(bitIndex)
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user