209 lines
5.8 KiB
Go
209 lines
5.8 KiB
Go
|
|
package infrastructure
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"slices"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||
|
|
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||
|
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||
|
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||
|
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||
|
|
)
|
||
|
|
|
||
|
|
func (ps *PubSubService) InitSubscribeEvents(ctx context.Context) error {
|
||
|
|
ourPeerID, err := oclib.GenerateNodeID()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
// subscribe :
|
||
|
|
if err := ps.subscribeEvents(ctx, nil, SEARCH, "", -1); err != nil { // we subscribe at our proprer deductible search adresse.
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := ps.subscribeEvents(ctx, nil, SEARCH, ourPeerID, -1); err != nil { // we subscribe at our proprer deductible search adresse.
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := ps.PartnersSubscribeEvents(ctx); err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ps *PubSubService) SearchSubscribeEvents(
|
||
|
|
ctx context.Context,
|
||
|
|
priv crypto.PrivKey,
|
||
|
|
dt tools.DataType,
|
||
|
|
id string,
|
||
|
|
payload []byte,
|
||
|
|
) error {
|
||
|
|
return ps.subscribeEvents(ctx, &dt, CREATE, id, 10)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ps *PubSubService) PartnersSubscribeEvents(
|
||
|
|
ctx context.Context,
|
||
|
|
) error {
|
||
|
|
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil)
|
||
|
|
peers := access.Search(nil, fmt.Sprintf("%v", peer.PARTNER.EnumIndex()), false)
|
||
|
|
|
||
|
|
for _, p := range peers.Data {
|
||
|
|
loadedPeer := access.LoadOne(p.GetID())
|
||
|
|
rp := loadedPeer.ToPeer()
|
||
|
|
if rp == nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if err := ps.PartnerSubscribeEvents(ctx, rp.PeerID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ps *PubSubService) PartnerSubscribeEvents(
|
||
|
|
ctx context.Context,
|
||
|
|
peerID string,
|
||
|
|
) error {
|
||
|
|
if peerID == "" {
|
||
|
|
return errors.New("should discover a particular peer")
|
||
|
|
}
|
||
|
|
if err := ps.subscribeEvents(ctx, nil, CREATE, peerID, -1); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := ps.subscribeEvents(ctx, nil, UPDATE, peerID, -1); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := ps.subscribeEvents(ctx, nil, DELETE, peerID, -1); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// generic function to subscribe to DHT flow of event
|
||
|
|
func (ps *PubSubService) subscribeEvents(
|
||
|
|
ctx context.Context,
|
||
|
|
dt *tools.DataType,
|
||
|
|
action PubSubAction,
|
||
|
|
peerID string,
|
||
|
|
timeout int,
|
||
|
|
) error {
|
||
|
|
// define a name app.action#peerID
|
||
|
|
name := "oc-catalog." + action.String() + "#" + peerID
|
||
|
|
if dt != nil { // if a datatype is precised then : app.action.datatype#peerID
|
||
|
|
name = "oc-catalog." + action.String() + "." + (*dt).String() + "#" + peerID
|
||
|
|
}
|
||
|
|
topic, err := ps.PS.Join(name) // find out the topic
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
sub, err := topic.Subscribe() // then subscribe to it
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
ps.mutex.Lock() // add safely in cache your subscription.
|
||
|
|
ps.Subscription = append(ps.Subscription, name)
|
||
|
|
ps.mutex.Unlock()
|
||
|
|
|
||
|
|
// launch loop waiting for results.
|
||
|
|
go ps.waitResults(ctx, sub, name, timeout)
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ps *PubSubService) waitResults(ctx context.Context, sub *pubsub.Subscription, topicName string, timeout int) {
|
||
|
|
logger := oclib.GetLogger()
|
||
|
|
defer ctx.Done()
|
||
|
|
for {
|
||
|
|
ps.mutex.Lock() // check safely if cache is actually notified subscribed to topic
|
||
|
|
if !slices.Contains(ps.Subscription, topicName) { // if not kill the loop.
|
||
|
|
break
|
||
|
|
}
|
||
|
|
ps.mutex.Unlock()
|
||
|
|
// if still subscribed -> wait for new message
|
||
|
|
var cancel context.CancelFunc
|
||
|
|
if timeout != -1 {
|
||
|
|
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
}
|
||
|
|
msg, err := sub.Next(ctx)
|
||
|
|
if err != nil {
|
||
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
||
|
|
// timeout hit, no message before deadline kill subsciption.
|
||
|
|
ps.mutex.Lock()
|
||
|
|
subs := []string{}
|
||
|
|
for _, ss := range ps.Subscription {
|
||
|
|
if ss != topicName {
|
||
|
|
subs = append(subs, ss)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ps.Subscription = subs
|
||
|
|
ps.mutex.Unlock()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
var evt Event
|
||
|
|
if err := json.Unmarshal(msg.Data, &evt); err != nil { // map to event
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil)
|
||
|
|
peers := access.Search(nil, evt.From, false)
|
||
|
|
if len(peers.Data) > 0 { // then we check if the peer is friendly or not : Partner or None...
|
||
|
|
if err := ps.processEventPeerKnown(ctx, peers, evt, topicName); err != nil {
|
||
|
|
logger.Err(err)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
tools.NewNATSCaller().SetNATSPub(tools.PEER_DISCOVERY, map[string]string{
|
||
|
|
"peer_id": evt.From,
|
||
|
|
})
|
||
|
|
time.Sleep(30 * time.Second)
|
||
|
|
peers = access.Search(nil, evt.From, false)
|
||
|
|
if len(peers.Data) > 0 { // if found... ok... if not found ignore
|
||
|
|
if err := ps.processEventPeerKnown(ctx, peers, evt, topicName); err != nil {
|
||
|
|
logger.Err(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ps *PubSubService) processEventPeerKnown(ctx context.Context, peers oclib.LibDataShallow, event Event, topicName string) error {
|
||
|
|
if err := ps.verifyPeer(peers, event); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
ps.handleEvent(ctx, topicName, event)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (pc *PubSubService) verifyPeer(peers oclib.LibDataShallow, event Event) error {
|
||
|
|
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil)
|
||
|
|
loadedPeer := access.LoadOne(peers.Data[0].GetID())
|
||
|
|
rp := loadedPeer.ToPeer()
|
||
|
|
if rp == nil || rp.Relation == peer.BLACKLIST { // if peer is blacklisted... quit...
|
||
|
|
return errors.New("peer is blacklisted")
|
||
|
|
}
|
||
|
|
pubKey, err := PubKeyFromString(rp.PublicKey) // extract pubkey from pubkey str
|
||
|
|
if err != nil {
|
||
|
|
return errors.New("pubkey is malformed")
|
||
|
|
}
|
||
|
|
data, _ := json.Marshal(event.rawEvent()) // extract byte from raw event excluding signature.
|
||
|
|
if ok, _ := pubKey.Verify(data, event.Signature); !ok { // then verify if pubkey sign this message...
|
||
|
|
return errors.New("check signature failed")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func PubKeyFromString(s string) (crypto.PubKey, error) {
|
||
|
|
data, err := base64.StdEncoding.DecodeString(s)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return crypto.UnmarshalPublicKey(data)
|
||
|
|
}
|