github.com/status-im/status-go@v1.1.0/wakuv2/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 "go.uber.org/zap" 27 "golang.org/x/exp/maps" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/log" 32 ) 33 34 // Filter represents a Waku message filter 35 type Filter struct { 36 Src *ecdsa.PublicKey // Sender of the message 37 KeyAsym *ecdsa.PrivateKey // Private Key of recipient 38 KeySym []byte // Key associated with the Topic 39 PubsubTopic string // Pubsub topic used to filter messages with 40 ContentTopics TopicSet // ContentTopics to filter messages with 41 SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization 42 id string // unique identifier 43 44 Messages MessageStore 45 } 46 47 type FilterSet = map[*Filter]struct{} 48 type ContentTopicToFilter = map[TopicType]FilterSet 49 type PubsubTopicToContentTopic = map[string]ContentTopicToFilter 50 51 // Filters represents a collection of filters 52 type Filters struct { 53 // Map of random ID to Filter 54 watchers map[string]*Filter 55 56 // Pubsub topic to use when no pubsub topic is specified on a filter 57 defaultPubsubTopic string 58 59 // map a topic to the filters that are interested in being notified when a message matches that topic 60 topicMatcher PubsubTopicToContentTopic 61 62 // list all the filters that will be notified of a new message, no matter what its topic is 63 allTopicsMatcher map[*Filter]struct{} 64 65 logger *zap.Logger 66 67 sync.RWMutex 68 } 69 70 // NewFilters returns a newly created filter collection 71 func NewFilters(defaultPubsubTopic string, logger *zap.Logger) *Filters { 72 return &Filters{ 73 watchers: make(map[string]*Filter), 74 topicMatcher: make(PubsubTopicToContentTopic), 75 allTopicsMatcher: make(map[*Filter]struct{}), 76 defaultPubsubTopic: defaultPubsubTopic, 77 logger: logger, 78 } 79 } 80 81 // Install will add a new filter to the filter collection 82 func (fs *Filters) Install(watcher *Filter) (string, error) { 83 if watcher.KeySym != nil && watcher.KeyAsym != nil { 84 return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys") 85 } 86 87 id, err := GenerateRandomID() 88 if err != nil { 89 return "", err 90 } 91 92 fs.Lock() 93 defer fs.Unlock() 94 95 if fs.watchers[id] != nil { 96 return "", fmt.Errorf("failed to generate unique ID") 97 } 98 99 if watcher.expectsSymmetricEncryption() { 100 watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) 101 } 102 103 watcher.id = id 104 105 fs.watchers[id] = watcher 106 fs.addTopicMatcher(watcher) 107 108 fs.logger.Debug("filters install", zap.String("id", id)) 109 return id, err 110 } 111 112 // Uninstall will remove a filter whose id has been specified from 113 // the filter collection 114 func (fs *Filters) Uninstall(id string) bool { 115 fs.Lock() 116 defer fs.Unlock() 117 watcher := fs.watchers[id] 118 if watcher != nil { 119 fs.removeFromTopicMatchers(watcher) 120 delete(fs.watchers, id) 121 122 fs.logger.Debug("filters uninstall", zap.String("id", id)) 123 return true 124 } 125 return false 126 } 127 128 func (fs *Filters) AllTopics() []TopicType { 129 var topics []TopicType 130 fs.Lock() 131 defer fs.Unlock() 132 for _, topicsPerPubsubTopic := range fs.topicMatcher { 133 for t := range topicsPerPubsubTopic { 134 topics = append(topics, t) 135 } 136 } 137 138 return topics 139 } 140 141 // addTopicMatcher adds a filter to the topic matchers. 142 // If the filter's Topics array is empty, it will be tried on every topic. 143 // Otherwise, it will be tried on the topics specified. 144 func (fs *Filters) addTopicMatcher(watcher *Filter) { 145 if len(watcher.ContentTopics) == 0 && (watcher.PubsubTopic == fs.defaultPubsubTopic || watcher.PubsubTopic == "") { 146 fs.allTopicsMatcher[watcher] = struct{}{} 147 } else { 148 filtersPerContentTopic, ok := fs.topicMatcher[watcher.PubsubTopic] 149 if !ok { 150 filtersPerContentTopic = make(ContentTopicToFilter) 151 } 152 153 for topic := range watcher.ContentTopics { 154 if filtersPerContentTopic[topic] == nil { 155 filtersPerContentTopic[topic] = make(FilterSet) 156 } 157 filtersPerContentTopic[topic][watcher] = struct{}{} 158 } 159 160 fs.topicMatcher[watcher.PubsubTopic] = filtersPerContentTopic 161 } 162 } 163 164 // removeFromTopicMatchers removes a filter from the topic matchers 165 func (fs *Filters) removeFromTopicMatchers(watcher *Filter) { 166 delete(fs.allTopicsMatcher, watcher) 167 168 filtersPerContentTopic, ok := fs.topicMatcher[watcher.PubsubTopic] 169 if !ok { 170 return 171 } 172 173 for topic := range watcher.ContentTopics { 174 delete(filtersPerContentTopic[topic], watcher) 175 } 176 177 fs.topicMatcher[watcher.PubsubTopic] = filtersPerContentTopic 178 } 179 180 // GetWatchersByTopic returns a slice containing the filters that 181 // match a specific topic 182 func (fs *Filters) GetWatchersByTopic(pubsubTopic string, contentTopic TopicType) []*Filter { 183 res := make([]*Filter, 0, len(fs.allTopicsMatcher)) 184 for watcher := range fs.allTopicsMatcher { 185 res = append(res, watcher) 186 } 187 188 filtersPerContentTopic, ok := fs.topicMatcher[pubsubTopic] 189 if !ok { 190 return res 191 } 192 193 for watcher := range filtersPerContentTopic[contentTopic] { 194 res = append(res, watcher) 195 } 196 return res 197 } 198 199 // Get returns a filter from the collection with a specific ID 200 func (fs *Filters) Get(id string) *Filter { 201 fs.RLock() 202 defer fs.RUnlock() 203 return fs.watchers[id] 204 } 205 206 func (fs *Filters) All() []*Filter { 207 fs.RLock() 208 defer fs.RUnlock() 209 var filters []*Filter 210 for _, f := range fs.watchers { 211 filters = append(filters, f) 212 } 213 return filters 214 } 215 216 func (fs *Filters) GetFilters() map[string]*Filter { 217 fs.RLock() 218 defer fs.RUnlock() 219 return maps.Clone(fs.watchers) 220 } 221 222 // NotifyWatchers notifies any filter that has declared interest 223 // for the envelope's topic. 224 func (fs *Filters) NotifyWatchers(recvMessage *ReceivedMessage) bool { 225 var decodedMsg *ReceivedMessage 226 227 fs.RLock() 228 defer fs.RUnlock() 229 230 var matched bool 231 candidates := fs.GetWatchersByTopic(recvMessage.PubsubTopic, recvMessage.ContentTopic) 232 233 if len(candidates) == 0 { 234 log.Debug("no filters available for this topic", "message", recvMessage.Hash().Hex(), "pubsubTopic", recvMessage.PubsubTopic, "contentTopic", recvMessage.ContentTopic.String()) 235 } 236 237 for _, watcher := range candidates { 238 // Messages are decrypted successfully only once 239 if decodedMsg == nil { 240 decodedMsg = recvMessage.Open(watcher) 241 if decodedMsg == nil { 242 log.Debug("processing message: failed to open", "message", recvMessage.Hash().Hex(), "filter", watcher.id) 243 continue 244 } 245 } 246 247 if watcher.MatchMessage(decodedMsg) { 248 matched = true 249 log.Debug("processing message: decrypted", "envelopeHash", recvMessage.Hash().Hex()) 250 if watcher.Src == nil || IsPubKeyEqual(decodedMsg.Src, watcher.Src) { 251 watcher.Trigger(decodedMsg) 252 } 253 } 254 } 255 256 return matched 257 } 258 259 func (f *Filter) expectsAsymmetricEncryption() bool { 260 return f.KeyAsym != nil 261 } 262 263 func (f *Filter) expectsSymmetricEncryption() bool { 264 return f.KeySym != nil 265 } 266 267 // Trigger adds a yet-unknown message to the filter's list of 268 // received messages. 269 func (f *Filter) Trigger(msg *ReceivedMessage) { 270 err := f.Messages.Add(msg) 271 if err != nil { 272 log.Error("failed to add msg into the filters store", "hash", msg.Hash(), "error", err) 273 } 274 } 275 276 // Retrieve will return the list of all received messages associated 277 // to a filter. 278 func (f *Filter) Retrieve() []*ReceivedMessage { 279 msgs, err := f.Messages.Pop() 280 if err != nil { 281 log.Error("failed to retrieve messages from filter store", "error", err) 282 return nil 283 } 284 return msgs 285 } 286 287 // MatchMessage checks if the filter matches an already decrypted 288 // message (i.e. a Message that has already been handled by 289 // MatchEnvelope when checked by a previous filter). 290 // Topics are not checked here, since this is done by topic matchers. 291 func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { 292 if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() { 293 return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) 294 } else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() { 295 return f.SymKeyHash == msg.SymKeyHash 296 } else if !f.expectsAsymmetricEncryption() && !f.expectsSymmetricEncryption() && !msg.isAsymmetricEncryption() && !msg.isSymmetricEncryption() { 297 return true 298 } 299 return false 300 }