2019-12-20 09:40:50 +00:00
|
|
|
// 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.
|
|
|
|
|
Move networking code for waku under `v0` namespace
Why make the change?
As discussed previously, the way we will move across versions is to maintain completely separate
codebases and eventually remove those that are not supported anymore.
This has the drawback of some code duplication, but the advantage is that is more
explicit what each version requires, and changes in one version will not
impact the other, so we won't pile up backward compatible code.
This is the same strategy used by `whisper` in go ethereum and is influenced by
https://www.youtube.com/watch?v=oyLBGkS5ICk .
All the code that is used for the networking protocol is now under `v0/`.
Some of the common parts might still be refactored out.
The main namespace `waku` deals with `host`->`waku` interactions (through RPC),
while `v0` deals with `waku`->`remote-waku` interactions.
In order to support `v1`, the namespace `v0` will be copied over, and changed to
support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed.
This PR does not actually implement `v1`, I'd rather get things looked over to
make sure the structure is what we would like before implementing the changes.
What has changed?
- Moved all code for the common parts under `waku/common/` namespace
- Moved code used for bloomfilters in `waku/common/bloomfilter.go`
- Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc)
- Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go`
Things still to do
Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`).
I started moving those tests to instead of stubbing using the actual component, which increases
the testing surface. Some other tests that can't be easily ported should be likely moved under
`v0` instead. Ideally no version specif code should be exported from a version namespace (for
example the various codes, as those might change across versions). But this will be a work-in-progress.
Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only
when implementations diverge across versions.
2020-04-21 12:40:30 +00:00
|
|
|
package common
|
2019-12-20 09:40:50 +00:00
|
|
|
|
|
|
|
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
|
Move networking code for waku under `v0` namespace
Why make the change?
As discussed previously, the way we will move across versions is to maintain completely separate
codebases and eventually remove those that are not supported anymore.
This has the drawback of some code duplication, but the advantage is that is more
explicit what each version requires, and changes in one version will not
impact the other, so we won't pile up backward compatible code.
This is the same strategy used by `whisper` in go ethereum and is influenced by
https://www.youtube.com/watch?v=oyLBGkS5ICk .
All the code that is used for the networking protocol is now under `v0/`.
Some of the common parts might still be refactored out.
The main namespace `waku` deals with `host`->`waku` interactions (through RPC),
while `v0` deals with `waku`->`remote-waku` interactions.
In order to support `v1`, the namespace `v0` will be copied over, and changed to
support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed.
This PR does not actually implement `v1`, I'd rather get things looked over to
make sure the structure is what we would like before implementing the changes.
What has changed?
- Moved all code for the common parts under `waku/common/` namespace
- Moved code used for bloomfilters in `waku/common/bloomfilter.go`
- Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc)
- Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go`
Things still to do
Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`).
I started moving those tests to instead of stubbing using the actual component, which increases
the testing surface. Some other tests that can't be easily ported should be likely moved under
`v0` instead. Ideally no version specif code should be exported from a version namespace (for
example the various codes, as those might change across versions). But this will be a work-in-progress.
Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only
when implementations diverge across versions.
2020-04-21 12:40:30 +00:00
|
|
|
func NewFilters() *Filters {
|
2019-12-20 09:40:50 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-01-14 22:15:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-20 09:40:50 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Move networking code for waku under `v0` namespace
Why make the change?
As discussed previously, the way we will move across versions is to maintain completely separate
codebases and eventually remove those that are not supported anymore.
This has the drawback of some code duplication, but the advantage is that is more
explicit what each version requires, and changes in one version will not
impact the other, so we won't pile up backward compatible code.
This is the same strategy used by `whisper` in go ethereum and is influenced by
https://www.youtube.com/watch?v=oyLBGkS5ICk .
All the code that is used for the networking protocol is now under `v0/`.
Some of the common parts might still be refactored out.
The main namespace `waku` deals with `host`->`waku` interactions (through RPC),
while `v0` deals with `waku`->`remote-waku` interactions.
In order to support `v1`, the namespace `v0` will be copied over, and changed to
support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed.
This PR does not actually implement `v1`, I'd rather get things looked over to
make sure the structure is what we would like before implementing the changes.
What has changed?
- Moved all code for the common parts under `waku/common/` namespace
- Moved code used for bloomfilters in `waku/common/bloomfilter.go`
- Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc)
- Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go`
Things still to do
Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`).
I started moving those tests to instead of stubbing using the actual component, which increases
the testing surface. Some other tests that can't be easily ported should be likely moved under
`v0` instead. Ideally no version specif code should be exported from a version namespace (for
example the various codes, as those might change across versions). But this will be a work-in-progress.
Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only
when implementations diverge across versions.
2020-04-21 12:40:30 +00:00
|
|
|
// GetWatchersByTopic returns a slice containing the filters that
|
2019-12-20 09:40:50 +00:00
|
|
|
// match a specific topic
|
Move networking code for waku under `v0` namespace
Why make the change?
As discussed previously, the way we will move across versions is to maintain completely separate
codebases and eventually remove those that are not supported anymore.
This has the drawback of some code duplication, but the advantage is that is more
explicit what each version requires, and changes in one version will not
impact the other, so we won't pile up backward compatible code.
This is the same strategy used by `whisper` in go ethereum and is influenced by
https://www.youtube.com/watch?v=oyLBGkS5ICk .
All the code that is used for the networking protocol is now under `v0/`.
Some of the common parts might still be refactored out.
The main namespace `waku` deals with `host`->`waku` interactions (through RPC),
while `v0` deals with `waku`->`remote-waku` interactions.
In order to support `v1`, the namespace `v0` will be copied over, and changed to
support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed.
This PR does not actually implement `v1`, I'd rather get things looked over to
make sure the structure is what we would like before implementing the changes.
What has changed?
- Moved all code for the common parts under `waku/common/` namespace
- Moved code used for bloomfilters in `waku/common/bloomfilter.go`
- Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc)
- Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go`
Things still to do
Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`).
I started moving those tests to instead of stubbing using the actual component, which increases
the testing surface. Some other tests that can't be easily ported should be likely moved under
`v0` instead. Ideally no version specif code should be exported from a version namespace (for
example the various codes, as those might change across versions). But this will be a work-in-progress.
Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only
when implementations diverge across versions.
2020-04-21 12:40:30 +00:00
|
|
|
func (fs *Filters) GetWatchersByTopic(topic TopicType) []*Filter {
|
2019-12-20 09:40:50 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
2023-12-15 16:16:18 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-20 09:40:50 +00:00
|
|
|
// NotifyWatchers notifies any filter that has declared interest
|
|
|
|
// for the envelope's topic.
|
2021-11-26 12:30:35 +00:00
|
|
|
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) bool {
|
2019-12-20 09:40:50 +00:00
|
|
|
var msg *ReceivedMessage
|
|
|
|
|
|
|
|
fs.mutex.RLock()
|
|
|
|
defer fs.mutex.RUnlock()
|
|
|
|
|
2021-11-26 12:30:35 +00:00
|
|
|
var matched bool
|
|
|
|
|
Move networking code for waku under `v0` namespace
Why make the change?
As discussed previously, the way we will move across versions is to maintain completely separate
codebases and eventually remove those that are not supported anymore.
This has the drawback of some code duplication, but the advantage is that is more
explicit what each version requires, and changes in one version will not
impact the other, so we won't pile up backward compatible code.
This is the same strategy used by `whisper` in go ethereum and is influenced by
https://www.youtube.com/watch?v=oyLBGkS5ICk .
All the code that is used for the networking protocol is now under `v0/`.
Some of the common parts might still be refactored out.
The main namespace `waku` deals with `host`->`waku` interactions (through RPC),
while `v0` deals with `waku`->`remote-waku` interactions.
In order to support `v1`, the namespace `v0` will be copied over, and changed to
support `v1`. Once `v0` will be not used anymore, the whole namespace will be removed.
This PR does not actually implement `v1`, I'd rather get things looked over to
make sure the structure is what we would like before implementing the changes.
What has changed?
- Moved all code for the common parts under `waku/common/` namespace
- Moved code used for bloomfilters in `waku/common/bloomfilter.go`
- Removed all version specific code from `waku/common/const` (`ProtocolVersion`, status-codes etc)
- Added interfaces for `WakuHost` and `Peer` under `waku/common/protocol.go`
Things still to do
Some tests in `waku/` are still testing by stubbing components of a particular version (`v0`).
I started moving those tests to instead of stubbing using the actual component, which increases
the testing surface. Some other tests that can't be easily ported should be likely moved under
`v0` instead. Ideally no version specif code should be exported from a version namespace (for
example the various codes, as those might change across versions). But this will be a work-in-progress.
Some code that will be common in `v0`/`v1` could still be extract to avoid duplication, and duplicated only
when implementations diverge across versions.
2020-04-21 12:40:30 +00:00
|
|
|
candidates := fs.GetWatchersByTopic(env.Topic)
|
2019-12-20 09:40:50 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-11-26 12:30:35 +00:00
|
|
|
matched = true
|
2019-12-20 09:40:50 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-26 12:30:35 +00:00
|
|
|
return matched
|
2019-12-20 09:40:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2021-11-26 12:30:35 +00:00
|
|
|
|
2019-12-20 09:40:50 +00:00
|
|
|
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
|
|
|
|
}
|