github.com/status-im/status-go@v1.1.0/waku/common/filter.go (about)

     1  // Copyright 2019 The Waku Library Authors.
     2  //
     3  // The Waku library is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Lesser General Public License as published by
     5  // the Free Software Foundation, either version 3 of the License, or
     6  // (at your option) any later version.
     7  //
     8  // The Waku library is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty off
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    11  // GNU Lesser General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Lesser General Public License
    14  // along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
    15  //
    16  // This software uses the go-ethereum library, which is licensed
    17  // under the GNU Lesser General Public Library, version 3 or any later.
    18  
    19  package common
    20  
    21  import (
    22  	"crypto/ecdsa"
    23  	"fmt"
    24  	"sync"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/log"
    29  )
    30  
    31  // Filter represents a Waku message filter
    32  type Filter struct {
    33  	Src        *ecdsa.PublicKey  // Sender of the message
    34  	KeyAsym    *ecdsa.PrivateKey // Private Key of recipient
    35  	KeySym     []byte            // Key associated with the Topic
    36  	Topics     [][]byte          // Topics to filter messages with
    37  	PoW        float64           // Proof of work as described in the Waku spec
    38  	AllowP2P   bool              // Indicates whether this filter is interested in direct peer-to-peer messages
    39  	SymKeyHash common.Hash       // The Keccak256Hash of the symmetric key, needed for optimization
    40  	id         string            // unique identifier
    41  
    42  	Messages MessageStore
    43  }
    44  
    45  // Filters represents a collection of filters
    46  type Filters struct {
    47  	watchers map[string]*Filter
    48  
    49  	topicMatcher     map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic
    50  	allTopicsMatcher map[*Filter]struct{}               // list all the filters that will be notified of a new message, no matter what its topic is
    51  
    52  	mutex sync.RWMutex
    53  }
    54  
    55  // NewFilters returns a newly created filter collection
    56  func NewFilters() *Filters {
    57  	return &Filters{
    58  		watchers:         make(map[string]*Filter),
    59  		topicMatcher:     make(map[TopicType]map[*Filter]struct{}),
    60  		allTopicsMatcher: make(map[*Filter]struct{}),
    61  	}
    62  }
    63  
    64  // Install will add a new filter to the filter collection
    65  func (fs *Filters) Install(watcher *Filter) (string, error) {
    66  	if watcher.KeySym != nil && watcher.KeyAsym != nil {
    67  		return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
    68  	}
    69  
    70  	id, err := GenerateRandomID()
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	fs.mutex.Lock()
    76  	defer fs.mutex.Unlock()
    77  
    78  	if fs.watchers[id] != nil {
    79  		return "", fmt.Errorf("failed to generate unique ID")
    80  	}
    81  
    82  	if watcher.expectsSymmetricEncryption() {
    83  		watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
    84  	}
    85  
    86  	watcher.id = id
    87  	fs.watchers[id] = watcher
    88  	fs.addTopicMatcher(watcher)
    89  	return id, err
    90  }
    91  
    92  // Uninstall will remove a filter whose id has been specified from
    93  // the filter collection
    94  func (fs *Filters) Uninstall(id string) bool {
    95  	fs.mutex.Lock()
    96  	defer fs.mutex.Unlock()
    97  	if fs.watchers[id] != nil {
    98  		fs.removeFromTopicMatchers(fs.watchers[id])
    99  		delete(fs.watchers, id)
   100  		return true
   101  	}
   102  	return false
   103  }
   104  
   105  func (fs *Filters) AllTopics() []TopicType {
   106  	var topics []TopicType
   107  	fs.mutex.Lock()
   108  	defer fs.mutex.Unlock()
   109  	for t := range fs.topicMatcher {
   110  		topics = append(topics, t)
   111  	}
   112  
   113  	return topics
   114  }
   115  
   116  // addTopicMatcher adds a filter to the topic matchers.
   117  // If the filter's Topics array is empty, it will be tried on every topic.
   118  // Otherwise, it will be tried on the topics specified.
   119  func (fs *Filters) addTopicMatcher(watcher *Filter) {
   120  	if len(watcher.Topics) == 0 {
   121  		fs.allTopicsMatcher[watcher] = struct{}{}
   122  	} else {
   123  		for _, t := range watcher.Topics {
   124  			topic := BytesToTopic(t)
   125  			if fs.topicMatcher[topic] == nil {
   126  				fs.topicMatcher[topic] = make(map[*Filter]struct{})
   127  			}
   128  			fs.topicMatcher[topic][watcher] = struct{}{}
   129  		}
   130  	}
   131  }
   132  
   133  // removeFromTopicMatchers removes a filter from the topic matchers
   134  func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
   135  	delete(fs.allTopicsMatcher, watcher)
   136  	for _, topic := range watcher.Topics {
   137  		delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
   138  	}
   139  }
   140  
   141  // GetWatchersByTopic returns a slice containing the filters that
   142  // match a specific topic
   143  func (fs *Filters) GetWatchersByTopic(topic TopicType) []*Filter {
   144  	res := make([]*Filter, 0, len(fs.allTopicsMatcher))
   145  	for watcher := range fs.allTopicsMatcher {
   146  		res = append(res, watcher)
   147  	}
   148  	for watcher := range fs.topicMatcher[topic] {
   149  		res = append(res, watcher)
   150  	}
   151  	return res
   152  }
   153  
   154  // Get returns a filter from the collection with a specific ID
   155  func (fs *Filters) Get(id string) *Filter {
   156  	fs.mutex.RLock()
   157  	defer fs.mutex.RUnlock()
   158  	return fs.watchers[id]
   159  }
   160  
   161  func (fs *Filters) All() []*Filter {
   162  	fs.mutex.RLock()
   163  	defer fs.mutex.RUnlock()
   164  	var filters []*Filter
   165  	for _, f := range fs.watchers {
   166  		filters = append(filters, f)
   167  	}
   168  	return filters
   169  }
   170  
   171  // NotifyWatchers notifies any filter that has declared interest
   172  // for the envelope's topic.
   173  func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) bool {
   174  	var msg *ReceivedMessage
   175  
   176  	fs.mutex.RLock()
   177  	defer fs.mutex.RUnlock()
   178  
   179  	var matched bool
   180  
   181  	candidates := fs.GetWatchersByTopic(env.Topic)
   182  	for _, watcher := range candidates {
   183  		if p2pMessage && !watcher.AllowP2P {
   184  			log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id))
   185  			continue
   186  		}
   187  
   188  		var match bool
   189  		if msg != nil {
   190  			match = watcher.MatchMessage(msg)
   191  		} else {
   192  			match = watcher.MatchEnvelope(env)
   193  			if match {
   194  				msg = env.Open(watcher)
   195  				if msg == nil {
   196  					log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
   197  				}
   198  			} else {
   199  				log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
   200  			}
   201  		}
   202  
   203  		if match && msg != nil {
   204  			msg.P2P = p2pMessage
   205  			log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
   206  			if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
   207  				watcher.Trigger(msg)
   208  			}
   209  			matched = true
   210  		}
   211  	}
   212  	return matched
   213  }
   214  
   215  func (f *Filter) expectsAsymmetricEncryption() bool {
   216  	return f.KeyAsym != nil
   217  }
   218  
   219  func (f *Filter) expectsSymmetricEncryption() bool {
   220  	return f.KeySym != nil
   221  }
   222  
   223  // Trigger adds a yet-unknown message to the filter's list of
   224  // received messages.
   225  func (f *Filter) Trigger(msg *ReceivedMessage) {
   226  	err := f.Messages.Add(msg)
   227  	if err != nil {
   228  		log.Error("failed to add msg into the filters store", "hash", msg.EnvelopeHash, "error", err)
   229  	}
   230  }
   231  
   232  // Retrieve will return the list of all received messages associated
   233  // to a filter.
   234  func (f *Filter) Retrieve() []*ReceivedMessage {
   235  	msgs, err := f.Messages.Pop()
   236  
   237  	if err != nil {
   238  		log.Error("failed to retrieve messages from filter store", "error", err)
   239  		return nil
   240  	}
   241  	return msgs
   242  }
   243  
   244  // MatchMessage checks if the filter matches an already decrypted
   245  // message (i.e. a Message that has already been handled by
   246  // MatchEnvelope when checked by a previous filter).
   247  // Topics are not checked here, since this is done by topic matchers.
   248  func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
   249  	if f.PoW > 0 && msg.PoW < f.PoW {
   250  		return false
   251  	}
   252  
   253  	if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
   254  		return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
   255  	} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
   256  		return f.SymKeyHash == msg.SymKeyHash
   257  	}
   258  	return false
   259  }
   260  
   261  // MatchEnvelope checks if it's worth decrypting the message. If
   262  // it returns `true`, client code is expected to attempt decrypting
   263  // the message and subsequently call MatchMessage.
   264  // Topics are not checked here, since this is done by topic matchers.
   265  func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
   266  	return f.PoW <= 0 || envelope.pow >= f.PoW
   267  }