go-waku/waku/v2/protocol/subscription/subscriptions_map.go

255 lines
5.9 KiB
Go
Raw Normal View History

package subscription
import (
2023-06-22 14:55:51 -04:00
"encoding/json"
"errors"
"sync"
"github.com/google/uuid"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol"
2023-05-08 17:33:10 -04:00
"go.uber.org/zap"
"golang.org/x/exp/maps"
)
type SubscriptionDetails struct {
sync.RWMutex
2023-05-08 17:33:10 -04:00
ID string
mapRef *SubscriptionsMap
2023-05-08 17:33:10 -04:00
Closed bool
once sync.Once
2023-05-08 17:33:10 -04:00
PeerID peer.ID
ContentFilter protocol.ContentFilter
C chan *protocol.Envelope
}
// Map of SubscriptionDetails.ID to subscriptions
type SubscriptionSet map[string]*SubscriptionDetails
type PeerSubscription struct {
PeerID peer.ID
SubsPerPubsubTopic map[string]SubscriptionSet
}
type SubscriptionsMap struct {
sync.RWMutex
2023-05-08 17:33:10 -04:00
logger *zap.Logger
Items map[peer.ID]*PeerSubscription
}
var ErrNotFound = errors.New("not found")
2023-05-08 17:33:10 -04:00
func NewSubscriptionMap(logger *zap.Logger) *SubscriptionsMap {
return &SubscriptionsMap{
2023-05-08 17:33:10 -04:00
logger: logger.Named("subscription-map"),
Items: make(map[peer.ID]*PeerSubscription),
}
}
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{
2023-05-08 17:33:10 -04:00
ID: uuid.NewString(),
mapRef: sub,
2023-05-08 17:33:10 -04:00
PeerID: peerID,
C: make(chan *protocol.Envelope, 1024),
ContentFilter: protocol.ContentFilter{PubsubTopic: cf.PubsubTopic, ContentTopics: maps.Clone(cf.ContentTopics)},
}
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()
2023-03-08 11:58:51 -04:00
// Check if peer exits
peerSubscription, ok := sub.Items[peerID]
2023-03-08 11:58:51 -04:00
if !ok {
return false
}
//TODO: Handle pubsubTopic as null
2023-03-08 11:58:51 -04:00
// Check if pubsub topic exists
subscriptions, ok := peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic]
2023-03-08 11:58:51 -04:00
if !ok {
return false
}
// Check if the content topic exists within the list of subscriptions for this peer
for _, ct := range cf.ContentTopicsList() {
2023-03-08 11:58:51 -04:00
found := false
for _, subscription := range subscriptions {
_, exists := subscription.ContentFilter.ContentTopics[ct]
2023-03-08 11:58:51 -04:00
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
}
delete(peerSubscription.SubsPerPubsubTopic[subscription.ContentFilter.PubsubTopic], subscription.ID)
return nil
}
func (s *SubscriptionDetails) Add(contentTopics ...string) {
s.Lock()
defer s.Unlock()
for _, ct := range contentTopics {
s.ContentFilter.ContentTopics[ct] = struct{}{}
}
}
func (s *SubscriptionDetails) Remove(contentTopics ...string) {
s.Lock()
defer s.Unlock()
for _, ct := range contentTopics {
delete(s.ContentFilter.ContentTopics, ct)
}
}
func (s *SubscriptionDetails) CloseC() {
s.once.Do(func() {
s.Lock()
defer s.Unlock()
2023-05-08 17:33:10 -04:00
s.Closed = true
close(s.C)
})
}
func (s *SubscriptionDetails) Close() error {
s.CloseC()
return s.mapRef.Delete(s)
}
2023-02-15 14:43:51 -04:00
func (s *SubscriptionDetails) Clone() *SubscriptionDetails {
s.RLock()
defer s.RUnlock()
result := &SubscriptionDetails{
2023-05-08 17:33:10 -04:00
ID: uuid.NewString(),
2023-02-15 14:43:51 -04:00
mapRef: s.mapRef,
2023-05-08 17:33:10 -04:00
Closed: false,
PeerID: s.PeerID,
ContentFilter: protocol.ContentFilter{PubsubTopic: s.ContentFilter.PubsubTopic, ContentTopics: maps.Clone(s.ContentFilter.ContentTopics)},
2023-02-15 14:43:51 -04:00
C: make(chan *protocol.Envelope),
}
return result
}
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 {
2023-05-08 17:33:10 -04:00
iterateSubscriptionSet(sub.logger, subscriptions, envelope)
}
}
2023-05-08 17:33:10 -04:00
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
}
2023-05-08 17:33:10 -04:00
if !subscription.Closed {
select {
case subscription.C <- envelope:
default:
logger.Warn("can't deliver message to subscription. subscriber too slow")
}
}
}(subscription)
}
}
2023-06-22 14:55:51 -04:00
func (s *SubscriptionDetails) MarshalJSON() ([]byte, error) {
type resultType struct {
PeerID string `json:"peerID"`
PubsubTopic string `json:"pubsubTopic"`
ContentTopics []string `json:"contentTopics"`
}
result := resultType{
PeerID: s.PeerID.Pretty(),
PubsubTopic: s.ContentFilter.PubsubTopic,
2023-06-22 14:55:51 -04:00
}
for c := range s.ContentFilter.ContentTopics {
2023-06-22 14:55:51 -04:00
result.ContentTopics = append(result.ContentTopics, c)
}
return json.Marshal(result)
}