mirror of https://github.com/status-im/go-waku.git
217 lines
5.7 KiB
Go
217 lines
5.7 KiB
Go
package subscription
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
type SubscriptionsMap struct {
|
|
sync.RWMutex
|
|
logger *zap.Logger
|
|
items map[peer.ID]*PeerSubscription
|
|
noOfSubs map[string]map[string]int
|
|
}
|
|
|
|
var ErrNotFound = errors.New("not found")
|
|
|
|
func NewSubscriptionMap(logger *zap.Logger) *SubscriptionsMap {
|
|
return &SubscriptionsMap{
|
|
logger: logger.Named("subscription-map"),
|
|
items: make(map[peer.ID]*PeerSubscription),
|
|
noOfSubs: map[string]map[string]int{},
|
|
}
|
|
}
|
|
|
|
func (m *SubscriptionsMap) IsListening(pubsubTopic, contentTopic string) bool {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
return m.noOfSubs[pubsubTopic] != nil && m.noOfSubs[pubsubTopic][contentTopic] > 0
|
|
}
|
|
|
|
func (m *SubscriptionsMap) increaseSubFor(pubsubTopic, contentTopic string) {
|
|
if m.noOfSubs[pubsubTopic] == nil {
|
|
m.noOfSubs[pubsubTopic] = map[string]int{}
|
|
}
|
|
m.noOfSubs[pubsubTopic][contentTopic] = m.noOfSubs[pubsubTopic][contentTopic] + 1
|
|
}
|
|
|
|
func (m *SubscriptionsMap) decreaseSubFor(pubsubTopic, contentTopic string) {
|
|
m.noOfSubs[pubsubTopic][contentTopic] = m.noOfSubs[pubsubTopic][contentTopic] - 1
|
|
}
|
|
|
|
func (sub *SubscriptionsMap) NewSubscription(peerID peer.ID, cf protocol.ContentFilter) *SubscriptionDetails {
|
|
sub.Lock()
|
|
defer sub.Unlock()
|
|
|
|
peerSubscription, ok := sub.items[peerID]
|
|
if !ok {
|
|
peerSubscription = &PeerSubscription{
|
|
PeerID: peerID,
|
|
SubsPerPubsubTopic: make(map[string]SubscriptionSet),
|
|
}
|
|
sub.items[peerID] = peerSubscription
|
|
}
|
|
|
|
_, ok = peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic]
|
|
if !ok {
|
|
peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic] = make(SubscriptionSet)
|
|
}
|
|
|
|
details := &SubscriptionDetails{
|
|
ID: uuid.NewString(),
|
|
mapRef: sub,
|
|
PeerID: peerID,
|
|
C: make(chan *protocol.Envelope, 1024),
|
|
ContentFilter: protocol.ContentFilter{PubsubTopic: cf.PubsubTopic, ContentTopics: maps.Clone(cf.ContentTopics)},
|
|
}
|
|
|
|
// Increase the number of subscriptions for this (pubsubTopic, contentTopic) pair
|
|
for contentTopic := range cf.ContentTopics {
|
|
sub.increaseSubFor(cf.PubsubTopic, contentTopic)
|
|
}
|
|
|
|
sub.items[peerID].SubsPerPubsubTopic[cf.PubsubTopic][details.ID] = details
|
|
|
|
return details
|
|
}
|
|
|
|
func (sub *SubscriptionsMap) IsSubscribedTo(peerID peer.ID) bool {
|
|
sub.RLock()
|
|
defer sub.RUnlock()
|
|
|
|
_, ok := sub.items[peerID]
|
|
return ok
|
|
}
|
|
|
|
// Check if we have subscriptions for all (pubsubTopic, contentTopics[i]) pairs provided
|
|
func (sub *SubscriptionsMap) Has(peerID peer.ID, cf protocol.ContentFilter) bool {
|
|
sub.RLock()
|
|
defer sub.RUnlock()
|
|
|
|
// Check if peer exits
|
|
peerSubscription, ok := sub.items[peerID]
|
|
if !ok {
|
|
return false
|
|
}
|
|
//TODO: Handle pubsubTopic as null
|
|
// Check if pubsub topic exists
|
|
subscriptions, ok := peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
// Check if the content topic exists within the list of subscriptions for this peer
|
|
for _, ct := range cf.ContentTopicsList() {
|
|
found := false
|
|
for _, subscription := range subscriptions {
|
|
_, exists := subscription.ContentFilter.ContentTopics[ct]
|
|
if exists {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
func (sub *SubscriptionsMap) Delete(subscription *SubscriptionDetails) error {
|
|
sub.Lock()
|
|
defer sub.Unlock()
|
|
|
|
peerSubscription, ok := sub.items[subscription.PeerID]
|
|
if !ok {
|
|
return ErrNotFound
|
|
}
|
|
|
|
contentFilter := subscription.ContentFilter
|
|
delete(peerSubscription.SubsPerPubsubTopic[contentFilter.PubsubTopic], subscription.ID)
|
|
|
|
// Decrease the number of subscriptions for this (pubsubTopic, contentTopic) pair
|
|
for contentTopic := range contentFilter.ContentTopics {
|
|
sub.decreaseSubFor(contentFilter.PubsubTopic, contentTopic)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sub *SubscriptionsMap) clear() {
|
|
for _, peerSubscription := range sub.items {
|
|
for _, subscriptionSet := range peerSubscription.SubsPerPubsubTopic {
|
|
for _, subscription := range subscriptionSet {
|
|
subscription.CloseC()
|
|
}
|
|
}
|
|
}
|
|
|
|
sub.items = make(map[peer.ID]*PeerSubscription)
|
|
}
|
|
|
|
func (sub *SubscriptionsMap) Clear() {
|
|
sub.Lock()
|
|
defer sub.Unlock()
|
|
sub.clear()
|
|
}
|
|
|
|
func (sub *SubscriptionsMap) Notify(peerID peer.ID, envelope *protocol.Envelope) {
|
|
sub.RLock()
|
|
defer sub.RUnlock()
|
|
|
|
subscriptions, ok := sub.items[peerID].SubsPerPubsubTopic[envelope.PubsubTopic()]
|
|
if ok {
|
|
iterateSubscriptionSet(sub.logger, subscriptions, envelope)
|
|
}
|
|
}
|
|
|
|
func iterateSubscriptionSet(logger *zap.Logger, subscriptions SubscriptionSet, envelope *protocol.Envelope) {
|
|
for _, subscription := range subscriptions {
|
|
func(subscription *SubscriptionDetails) {
|
|
subscription.RLock()
|
|
defer subscription.RUnlock()
|
|
|
|
_, ok := subscription.ContentFilter.ContentTopics[envelope.Message().ContentTopic]
|
|
if !ok { // only send the msg to subscriptions that have matching contentTopic
|
|
return
|
|
}
|
|
|
|
if !subscription.Closed {
|
|
select {
|
|
case subscription.C <- envelope:
|
|
default:
|
|
logger.Warn("can't deliver message to subscription. subscriber too slow")
|
|
}
|
|
}
|
|
}(subscription)
|
|
}
|
|
}
|
|
|
|
func (m *SubscriptionsMap) GetSubscription(peerID peer.ID, contentFilter protocol.ContentFilter) ([]peer.ID, []*SubscriptionDetails) {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
var output []*SubscriptionDetails
|
|
|
|
var peerIDs []peer.ID
|
|
for _, peerSubs := range m.items {
|
|
if peerID == "" || peerSubs.PeerID == peerID {
|
|
peerIDs = append(peerIDs, peerID)
|
|
for _, subs := range peerSubs.SubsPerPubsubTopic {
|
|
for _, subscriptionDetail := range subs {
|
|
if subscriptionDetail.isPartOf(contentFilter) {
|
|
output = append(output, subscriptionDetail)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return peerIDs, output
|
|
}
|