github.com/ethersphere/bee/v2@v2.2.0/pkg/pss/pss.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package pss exposes functionalities needed to communicate
     6  // with other peers on the network. Pss uses pushsync and
     7  // pullsync for message delivery and mailboxing. All messages are disguised as content-addressed chunks. Sending and
     8  // receiving of messages is exposed over the HTTP API, with
     9  // websocket subscriptions for incoming messages.
    10  package pss
    11  
    12  import (
    13  	"context"
    14  	"crypto/ecdsa"
    15  	"errors"
    16  	"io"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/ethersphere/bee/v2/pkg/log"
    21  	"github.com/ethersphere/bee/v2/pkg/postage"
    22  	"github.com/ethersphere/bee/v2/pkg/pushsync"
    23  	"github.com/ethersphere/bee/v2/pkg/swarm"
    24  	"github.com/ethersphere/bee/v2/pkg/topology"
    25  )
    26  
    27  // loggerName is the tree path name of the logger for this package.
    28  const loggerName = "pss"
    29  
    30  var (
    31  	_            Interface = (*pss)(nil)
    32  	ErrNoHandler           = errors.New("no handler found")
    33  )
    34  
    35  type Sender interface {
    36  	// Send arbitrary byte slice with the given topic to Targets.
    37  	Send(context.Context, Topic, []byte, postage.Stamper, *ecdsa.PublicKey, Targets) error
    38  }
    39  
    40  type Interface interface {
    41  	Sender
    42  	// Register a Handler for a given Topic.
    43  	Register(Topic, Handler) func()
    44  	// TryUnwrap tries to unwrap a wrapped trojan message.
    45  	TryUnwrap(swarm.Chunk)
    46  
    47  	SetPushSyncer(pushSyncer pushsync.PushSyncer)
    48  	io.Closer
    49  }
    50  
    51  type pss struct {
    52  	key        *ecdsa.PrivateKey
    53  	pusher     pushsync.PushSyncer
    54  	handlers   map[Topic][]*Handler
    55  	handlersMu sync.Mutex
    56  	metrics    metrics
    57  	logger     log.Logger
    58  	quit       chan struct{}
    59  }
    60  
    61  // New returns a new pss service.
    62  func New(key *ecdsa.PrivateKey, logger log.Logger) Interface {
    63  	return &pss{
    64  		key:      key,
    65  		logger:   logger.WithName(loggerName).Register(),
    66  		handlers: make(map[Topic][]*Handler),
    67  		metrics:  newMetrics(),
    68  		quit:     make(chan struct{}),
    69  	}
    70  }
    71  
    72  func (ps *pss) Close() error {
    73  	close(ps.quit)
    74  	ps.handlersMu.Lock()
    75  	defer ps.handlersMu.Unlock()
    76  
    77  	ps.handlers = make(map[Topic][]*Handler) //unset handlers on shutdown
    78  
    79  	return nil
    80  }
    81  
    82  func (ps *pss) SetPushSyncer(pushSyncer pushsync.PushSyncer) {
    83  	ps.pusher = pushSyncer
    84  }
    85  
    86  // Handler defines code to be executed upon reception of a trojan message.
    87  type Handler func(context.Context, []byte)
    88  
    89  // Send constructs a padded message with topic and payload,
    90  // wraps it in a trojan chunk such that one of the targets is a prefix of the chunk address.
    91  // Uses push-sync to deliver message.
    92  func (p *pss) Send(ctx context.Context, topic Topic, payload []byte, stamper postage.Stamper, recipient *ecdsa.PublicKey, targets Targets) error {
    93  	p.metrics.TotalMessagesSentCounter.Inc()
    94  
    95  	tStart := time.Now()
    96  
    97  	tc, err := Wrap(ctx, topic, payload, recipient, targets)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	stamp, err := stamper.Stamp(tc.Address())
   103  	if err != nil {
   104  		return err
   105  	}
   106  	tc = tc.WithStamp(stamp)
   107  
   108  	p.metrics.MessageMiningDuration.Set(time.Since(tStart).Seconds())
   109  
   110  	// push the chunk using push sync so that it reaches it destination in network
   111  	if _, err = p.pusher.PushChunkToClosest(ctx, tc); err != nil {
   112  		if errors.Is(err, topology.ErrWantSelf) {
   113  			return nil
   114  		}
   115  		return err
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Register allows the definition of a Handler func for a specific topic on the pss struct.
   122  func (p *pss) Register(topic Topic, handler Handler) (cleanup func()) {
   123  	p.handlersMu.Lock()
   124  	defer p.handlersMu.Unlock()
   125  
   126  	p.handlers[topic] = append(p.handlers[topic], &handler)
   127  
   128  	return func() {
   129  		p.handlersMu.Lock()
   130  		defer p.handlersMu.Unlock()
   131  
   132  		h := p.handlers[topic]
   133  		for i := 0; i < len(h); i++ {
   134  			if h[i] == &handler {
   135  				p.handlers[topic] = append(h[:i], h[i+1:]...)
   136  				return
   137  			}
   138  		}
   139  	}
   140  }
   141  
   142  func (p *pss) topics() []Topic {
   143  	p.handlersMu.Lock()
   144  	defer p.handlersMu.Unlock()
   145  
   146  	ts := make([]Topic, 0, len(p.handlers))
   147  	for t := range p.handlers {
   148  		ts = append(ts, t)
   149  	}
   150  
   151  	return ts
   152  }
   153  
   154  // TryUnwrap allows unwrapping a chunk as a trojan message and calling its handlers based on the topic.
   155  func (p *pss) TryUnwrap(c swarm.Chunk) {
   156  	if len(c.Data()) < swarm.ChunkWithSpanSize {
   157  		return // chunk not full
   158  	}
   159  	ctx := context.Background()
   160  	topic, msg, err := Unwrap(ctx, p.key, c, p.topics())
   161  	if err != nil {
   162  		return // cannot unwrap
   163  	}
   164  	h := p.getHandlers(topic)
   165  	if h == nil {
   166  		return // no handler
   167  	}
   168  
   169  	ctx, cancel := context.WithCancel(ctx)
   170  	done := make(chan struct{})
   171  	var wg sync.WaitGroup
   172  	go func() {
   173  		defer cancel()
   174  		select {
   175  		case <-p.quit:
   176  		case <-done:
   177  		}
   178  	}()
   179  	for _, hh := range h {
   180  		wg.Add(1)
   181  		go func(hh Handler) {
   182  			defer wg.Done()
   183  			hh(ctx, msg)
   184  		}(*hh)
   185  	}
   186  	go func() {
   187  		wg.Wait()
   188  		close(done)
   189  	}()
   190  }
   191  
   192  func (p *pss) getHandlers(topic Topic) []*Handler {
   193  	p.handlersMu.Lock()
   194  	defer p.handlersMu.Unlock()
   195  
   196  	return p.handlers[topic]
   197  }