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 }