Daemons Search
This commit is contained in:
429
daemons/dht/dht.go
Normal file
429
daemons/dht/dht.go
Normal file
@@ -0,0 +1,429 @@
|
||||
package dht
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"oc-discovery/conf"
|
||||
"oc-discovery/daemons"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
pp "cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
"github.com/google/uuid"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
dht "github.com/libp2p/go-libp2p-kad-dht"
|
||||
kad_dht "github.com/libp2p/go-libp2p-kad-dht"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
type DHTRecord struct {
|
||||
Name string `json:"name"`
|
||||
State int `json:"state"`
|
||||
DID string `json:"did"`
|
||||
PeerID string `json:"peer_id"`
|
||||
PubKey []byte `json:"pub_key"`
|
||||
URL string `json:"url"`
|
||||
NATSUrl string `json:"nats_url"`
|
||||
Wallet string `json:"wallet"`
|
||||
Signature []byte `json:"signature"`
|
||||
ExpiryDate time.Time `json:"expiry_date"`
|
||||
}
|
||||
|
||||
type DHTService struct {
|
||||
Key string
|
||||
Host host.Host
|
||||
DHT *dht.IpfsDHT
|
||||
Cache []string
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// TODO kick connection to base... and send on NATS boy
|
||||
var dhtSingletonService *DHTService
|
||||
|
||||
func GetDHTService() *DHTService {
|
||||
return dhtSingletonService
|
||||
}
|
||||
|
||||
func Init(ctx context.Context) (*DHTService, error) {
|
||||
service := &DHTService{}
|
||||
priv, err := daemons.LoadKeyFromFile(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
psk, err := daemons.LoadPSKFromFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := libp2p.New(
|
||||
libp2p.PrivateNetwork(psk),
|
||||
libp2p.Identity(priv),
|
||||
libp2p.ListenAddrStrings(
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", conf.GetConfig().DHTEndpointPort),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service.Host = h
|
||||
service.DHT, err = kad_dht.New(ctx, h, kad_dht.MaxRecordAge(24*time.Hour)) // every day DHT will purge expired data... if not used.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = service.DHT.Bootstrap(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, address := range strings.Split(conf.GetConfig().BootstrapAddresses, ",") {
|
||||
pi, err := peer.AddrInfoFromString(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := oclib.GetLogger()
|
||||
if err := h.Connect(ctx, *pi); err != nil {
|
||||
logger.Err(fmt.Errorf("Failed to connect to MAIN bootstrap peer %s: %s", pi.ID, err))
|
||||
} else {
|
||||
logger.Info().Msg(fmt.Sprintf("Connected to MAIN bootstrap peer %s", pi.ID))
|
||||
}
|
||||
}
|
||||
|
||||
dhtSingletonService = service
|
||||
if daemons.VerifyPubWithPriv() {
|
||||
if _, err := dhtSingletonService.ClaimName(context.Background(),
|
||||
conf.GetConfig().Name,
|
||||
conf.GetConfig().Hostname, false); err == nil {
|
||||
go service.Heartbeat(ctx, 2*time.Minute)
|
||||
go service.RefreshKeys(ctx, 30*time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (d *DHTService) Heartbeat(ctx context.Context, interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
dhtSingletonService.ClaimName(context.Background(), conf.GetConfig().Name, conf.GetConfig().Hostname, true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *DHTService) RefreshKeys(ctx context.Context, interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
s := []string{}
|
||||
d.mutex.Lock()
|
||||
s = append(s, d.Cache...)
|
||||
d.mutex.Unlock()
|
||||
for _, key := range s {
|
||||
_, _ = d.GetValue(ctx, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *DHTService) PutValue(
|
||||
ctx context.Context,
|
||||
key string,
|
||||
value []byte,
|
||||
) error {
|
||||
err := d.DHT.PutValue(ctx, key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.mutex.Lock()
|
||||
if !slices.Contains(d.Cache, key) {
|
||||
d.Cache = append(d.Cache, key)
|
||||
}
|
||||
d.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DHTService) GetValue(
|
||||
ctx context.Context,
|
||||
key string,
|
||||
) (*DHTRecord, error) {
|
||||
dht, err := d.DHT.GetValue(ctx, key)
|
||||
if err != nil {
|
||||
cache := []string{}
|
||||
d.mutex.Lock()
|
||||
for _, c := range d.Cache {
|
||||
if c != key {
|
||||
cache = append(cache, c)
|
||||
}
|
||||
}
|
||||
d.Cache = cache
|
||||
d.mutex.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
d.mutex.Lock()
|
||||
if !slices.Contains(d.Cache, key) {
|
||||
d.Cache = append(d.Cache, key)
|
||||
}
|
||||
d.mutex.Unlock()
|
||||
var data DHTRecord
|
||||
json.Unmarshal(dht, &data)
|
||||
|
||||
peerID, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &pp.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: data.Name,
|
||||
},
|
||||
State: pp.ONLINE,
|
||||
Relation: pp.SELF,
|
||||
PeerID: peerID,
|
||||
PublicKey: string(data.PubKey),
|
||||
Url: data.URL,
|
||||
NATSUrl: oclib.GetConfig().NATSUrl,
|
||||
WalletAddress: data.Wallet,
|
||||
}
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.PEER,
|
||||
Method: int(tools.CREATE_PEER),
|
||||
Payload: b,
|
||||
})
|
||||
|
||||
/*if founded, _, err := access.Search(nil, fmt.Sprintf("%v", pp.SELF.EnumIndex()), false);
|
||||
err == nil && len(founded) > 0 && founded[0].(*pp.Peer).Relation != pp.BLACKLIST {
|
||||
f.(*pp.Peer).State = pp.ONLINE
|
||||
f.(*pp.Peer).NATSUrl = p.NATSUrl
|
||||
f.(*pp.Peer).Url = p.Url
|
||||
f.(*pp.Peer).PeerID = p.PeerID
|
||||
f.(*pp.Peer).Relation = p.Relation
|
||||
f.(*pp.Peer).WalletAddress = p.WalletAddress
|
||||
access.UpdateOne(f, f.GetID())
|
||||
}*/
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func (d *DHTService) generateKey() (string, error) {
|
||||
s, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
return "/opencloud/peer/" + s, nil
|
||||
}
|
||||
|
||||
// Create your peer.
|
||||
func (d *DHTService) ClaimName(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
endPoint string,
|
||||
avoidVerification bool,
|
||||
) (*pp.Peer, error) {
|
||||
if endPoint == "" {
|
||||
return nil, errors.New("no endpoint found for peer" + name)
|
||||
}
|
||||
|
||||
peerID, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pub := d.Host.Peerstore().PubKey(d.Host.ID())
|
||||
pubBytes, _ := pub.Raw()
|
||||
|
||||
now := time.Now()
|
||||
expiry := now.Add(150 * time.Second)
|
||||
|
||||
rec := DHTRecord{
|
||||
Name: name,
|
||||
PeerID: peerID,
|
||||
PubKey: pubBytes,
|
||||
}
|
||||
|
||||
payload, _ := json.Marshal(rec)
|
||||
sig, _ := daemons.Sign(d.Host.Peerstore().PrivKey(d.Host.ID()), payload)
|
||||
rec.Signature = sig
|
||||
|
||||
rec.URL = endPoint
|
||||
rec.NATSUrl = oclib.GetConfig().NATSUrl
|
||||
rec.State = pp.ONLINE.EnumIndex()
|
||||
rec.ExpiryDate = expiry
|
||||
|
||||
data, _ := json.Marshal(rec)
|
||||
|
||||
key, err := d.generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// retrieve your key name in standard
|
||||
if !avoidVerification {
|
||||
old, err := d.GetValue(ctx, key)
|
||||
if err == nil {
|
||||
if old.PeerID != peerID { // check if someone claims your name before
|
||||
return nil, errors.New("name already claimed by another peer")
|
||||
}
|
||||
if now.After(old.ExpiryDate) {
|
||||
payload, _ := json.Marshal(rec)
|
||||
d.PutValue(ctx, key, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.PutValue(ctx, key, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubStr := base64.StdEncoding.EncodeToString(pubBytes)
|
||||
d.Key = key
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &pp.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: name,
|
||||
},
|
||||
State: pp.ONLINE,
|
||||
Relation: pp.SELF,
|
||||
PeerID: peerID,
|
||||
PublicKey: pubStr,
|
||||
Url: endPoint,
|
||||
NATSUrl: oclib.GetConfig().NATSUrl,
|
||||
WalletAddress: "my-wallet",
|
||||
}
|
||||
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.PEER,
|
||||
Method: int(tools.CREATE_PEER),
|
||||
Payload: b,
|
||||
})
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Discover a specific Peer
|
||||
func (d *DHTService) DiscoverPeers(ctx context.Context, name string) ([]*pp.Peer, error) {
|
||||
peers := []*pp.Peer{}
|
||||
key, err := d.generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
datas, _ := d.DHT.SearchValue(ctx, key)
|
||||
for data := range datas {
|
||||
var dht *DHTRecord
|
||||
if err := json.Unmarshal(data, dht); err != nil {
|
||||
return peers, err
|
||||
}
|
||||
if p, err := d.treatPeer(ctx, key, dht); err == nil {
|
||||
peers = append(peers, p)
|
||||
}
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func (d *DHTService) GetPeer(ctx context.Context, name string) (*pp.Peer, error) {
|
||||
key, err := d.generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := d.GetValue(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errors.New("no DHT peer not found")
|
||||
}
|
||||
return d.treatPeer(ctx, key, data)
|
||||
}
|
||||
|
||||
func (d *DHTService) treatPeer(ctx context.Context, key string, rec *DHTRecord) (*pp.Peer, error) {
|
||||
pubKey, err := crypto.UnmarshalPublicKey(rec.PubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dht := DHTRecord{
|
||||
Name: rec.Name,
|
||||
PeerID: rec.PeerID,
|
||||
PubKey: rec.PubKey,
|
||||
}
|
||||
payload, _ := json.Marshal(dht)
|
||||
|
||||
if ok, _ := daemons.Verify(pubKey, payload, rec.Signature); !ok {
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
pubBytes, _ := pubKey.Raw()
|
||||
pubStr := base64.StdEncoding.EncodeToString(pubBytes)
|
||||
|
||||
rel := pp.NONE
|
||||
if d.Key == key {
|
||||
rel = pp.SELF
|
||||
}
|
||||
|
||||
p := &pp.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: rec.Name,
|
||||
},
|
||||
State: pp.ONLINE,
|
||||
Relation: rel,
|
||||
PeerID: rec.PeerID,
|
||||
PublicKey: pubStr,
|
||||
Url: rec.URL,
|
||||
NATSUrl: rec.NATSUrl,
|
||||
WalletAddress: rec.Wallet,
|
||||
}
|
||||
if now.After(rec.ExpiryDate) { // is expired
|
||||
rec.State = pp.OFFLINE.EnumIndex()
|
||||
p.State = pp.OFFLINE
|
||||
payload, _ := json.Marshal(rec)
|
||||
d.PutValue(ctx, key, payload)
|
||||
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.PEER,
|
||||
Method: int(tools.CREATE_PEER),
|
||||
Payload: b,
|
||||
})
|
||||
return nil, errors.New("peer " + key + " is expired")
|
||||
}
|
||||
if p.State == pp.OFFLINE {
|
||||
return nil, errors.New("peer " + key + " is offline")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// TODO : HEARTBEAT
|
||||
89
daemons/pubsub/handler.go
Normal file
89
daemons/pubsub/handler.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"oc-discovery/daemons"
|
||||
"oc-discovery/daemons/dht"
|
||||
"oc-discovery/models"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
)
|
||||
|
||||
func (ps *PubSubService) handleEvent(ctx context.Context, topicName string, evt models.Event) error {
|
||||
action := ps.getTopicName(topicName)
|
||||
if err := ps.handleEventFromPartner(evt, action); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ps.handleEventSearch(ctx, evt, action); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) handleEventFromPartner(evt models.Event, action tools.PubSubAction) error {
|
||||
if !(action == tools.PB_CREATE || action == tools.PB_UPDATE || action == tools.PB_DELETE) {
|
||||
return nil
|
||||
}
|
||||
resource, err := resources.ToResource(int(evt.DataType), evt.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch action {
|
||||
case tools.PB_CREATE:
|
||||
case tools.PB_UPDATE:
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.DataType(evt.DataType),
|
||||
Method: int(tools.CREATE_RESOURCE),
|
||||
Payload: b,
|
||||
})
|
||||
case tools.PB_DELETE:
|
||||
tools.NewNATSCaller().SetNATSPub(tools.REMOVE_RESOURCE, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.DataType(evt.DataType),
|
||||
Method: int(tools.REMOVE_RESOURCE),
|
||||
Payload: b,
|
||||
})
|
||||
default:
|
||||
return errors.New("no action authorized available : " + action.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) handleEventSearch( // only : on partner followings. 3 canals for every partner.
|
||||
ctx context.Context,
|
||||
evt models.Event,
|
||||
action tools.PubSubAction,
|
||||
) error {
|
||||
if !(action == tools.PB_SEARCH_RESPONSE || action == tools.PB_SEARCH) {
|
||||
return nil
|
||||
}
|
||||
if p, err := dht.GetDHTService().GetPeer(ctx, evt.From); err == nil {
|
||||
if err := daemons.VerifyPeer([]*peer.Peer{p}, evt); err != nil {
|
||||
return err
|
||||
}
|
||||
switch action {
|
||||
case tools.PB_SEARCH_RESPONSE:
|
||||
if err := ps.retrieveResponse(ctx, p, evt); err != nil {
|
||||
return err
|
||||
}
|
||||
case tools.PB_SEARCH: // when someone ask for search.
|
||||
if p, err := dht.GetDHTService().GetPeer(ctx, evt.From); err == nil {
|
||||
if err := ps.sendResponse(ctx, p, evt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
47
daemons/pubsub/nats.go
Normal file
47
daemons/pubsub/nats.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
)
|
||||
|
||||
func ListenNATS() {
|
||||
tools.NewNATSCaller().ListenNats(map[tools.NATSMethod]func(tools.NATSResponse){
|
||||
tools.PROPALGATION_EVENT: func(resp tools.NATSResponse) {
|
||||
var propalgation tools.PropalgationMessage
|
||||
err := json.Unmarshal(resp.Payload, &propalgation)
|
||||
var dt *tools.DataType
|
||||
if propalgation.DataType > 0 {
|
||||
dtt := tools.DataType(propalgation.DataType)
|
||||
dt = &dtt
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
switch propalgation.Action {
|
||||
case tools.PB_CREATE:
|
||||
case tools.PB_UPDATE:
|
||||
case tools.PB_DELETE:
|
||||
GetPubSubService().ToPartnerPublishEvent(
|
||||
context.Background(),
|
||||
propalgation.Action,
|
||||
dt, propalgation.User,
|
||||
propalgation.Payload,
|
||||
)
|
||||
case tools.PB_SEARCH:
|
||||
m := map[string]interface{}{}
|
||||
json.Unmarshal(propalgation.Payload, &m)
|
||||
GetPubSubService().SearchPublishEvent(
|
||||
context.Background(),
|
||||
dt,
|
||||
fmt.Sprintf("%v", m["type"]),
|
||||
propalgation.User,
|
||||
fmt.Sprintf("%v", m["search"]),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
205
daemons/pubsub/publish.go
Normal file
205
daemons/pubsub/publish.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"oc-discovery/daemons"
|
||||
"oc-discovery/models"
|
||||
"time"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
)
|
||||
|
||||
func (ps *PubSubService) SearchPublishEvent(
|
||||
ctx context.Context,
|
||||
dt *tools.DataType,
|
||||
typ string,
|
||||
user string,
|
||||
search string,
|
||||
) error {
|
||||
switch typ {
|
||||
case "partner":
|
||||
ps.searchPartnersPublishEvent(
|
||||
ctx, dt, user, search,
|
||||
)
|
||||
case "all":
|
||||
b, err := json.Marshal(map[string]string{
|
||||
"search": search,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ps.searchPublishEvent(
|
||||
ctx, dt, user, "", b,
|
||||
)
|
||||
case "known":
|
||||
ps.searchKnownPublishEvent(
|
||||
ctx, dt, user, search,
|
||||
)
|
||||
default:
|
||||
return errors.New("no type of research found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) searchPartnersPublishEvent(
|
||||
ctx context.Context,
|
||||
dt *tools.DataType,
|
||||
user string,
|
||||
search string,
|
||||
) error {
|
||||
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
|
||||
f := &dbs.Filters{
|
||||
And: map[string][]dbs.Filter{ // search by name if no filters are provided
|
||||
"state": {{Operator: dbs.EQUAL.String(), Value: peer.ONLINE.EnumIndex()}},
|
||||
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.PARTNER.EnumIndex()}},
|
||||
},
|
||||
}
|
||||
if search != "" {
|
||||
f.Or = map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.type": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.owners.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(map[string]string{
|
||||
"search": search,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peersKnown := access.Search(f, "", false)
|
||||
for _, known := range peersKnown.Data {
|
||||
if err := ps.searchPublishEvent(ctx, dt, user, known.GetID(), b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) searchKnownPublishEvent(
|
||||
ctx context.Context,
|
||||
dt *tools.DataType,
|
||||
user string,
|
||||
search string,
|
||||
) error {
|
||||
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
|
||||
f := &dbs.Filters{
|
||||
And: map[string][]dbs.Filter{ // search by name if no filters are provided
|
||||
"state": {{Operator: dbs.EQUAL.String(), Value: peer.ONLINE.EnumIndex()}},
|
||||
"relation": {{Operator: dbs.NOT.String(), Value: &dbs.Filters{
|
||||
And: map[string][]dbs.Filter{
|
||||
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.BLACKLIST.EnumIndex()}},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}
|
||||
if search != "" {
|
||||
f.Or = map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.type": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.owners.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(map[string]string{
|
||||
"search": search,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peersKnown := access.Search(f, "", false)
|
||||
for _, known := range peersKnown.Data {
|
||||
if err := ps.searchPublishEvent(ctx, dt, user, known.GetID(), b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) searchPublishEvent(
|
||||
ctx context.Context,
|
||||
dt *tools.DataType,
|
||||
user string,
|
||||
peerID string,
|
||||
payload []byte,
|
||||
) error {
|
||||
id, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ps.subscribeEvents(ctx, dt, tools.PB_SEARCH_RESPONSE, id, 60); err != nil { // TODO Catpure Event !
|
||||
return err
|
||||
}
|
||||
return ps.publishEvent(ctx, dt, tools.PB_SEARCH, user, peerID, payload, false)
|
||||
}
|
||||
|
||||
func (ps *PubSubService) ToPartnerPublishEvent(
|
||||
ctx context.Context, action tools.PubSubAction, dt *tools.DataType, user string, payload []byte) error {
|
||||
id, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ps.publishEvent(ctx, dt, action, user, id, payload, false)
|
||||
}
|
||||
|
||||
func (ps *PubSubService) publishEvent(
|
||||
ctx context.Context,
|
||||
dt *tools.DataType,
|
||||
action tools.PubSubAction,
|
||||
user string,
|
||||
peerID string,
|
||||
payload []byte,
|
||||
chanNamedByDt bool,
|
||||
) error {
|
||||
name := action.String() + "#" + peerID
|
||||
if chanNamedByDt && dt != nil { // if a datatype is precised then : app.action.datatype#peerID
|
||||
name = action.String() + "." + (*dt).String() + "#" + peerID
|
||||
}
|
||||
|
||||
from, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evt := models.Event{
|
||||
Type: name,
|
||||
From: from,
|
||||
User: user,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Payload: payload,
|
||||
}
|
||||
if dt != nil {
|
||||
evt.DataType = int64(dt.EnumIndex())
|
||||
} else {
|
||||
evt.DataType = -1
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(evt)
|
||||
priv, err := daemons.LoadKeyFromFile(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig, _ := priv.Sign(body)
|
||||
evt.Signature = sig
|
||||
|
||||
msg, _ := json.Marshal(evt)
|
||||
|
||||
topic, err := ps.PS.Join(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return topic.Publish(ctx, msg)
|
||||
}
|
||||
|
||||
// TODO REVIEW PUBLISHING + ADD SEARCH ON PUBLIC : YES
|
||||
// TODO : Search should verify DataType
|
||||
127
daemons/pubsub/service.go
Normal file
127
daemons/pubsub/service.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"oc-discovery/models"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
)
|
||||
|
||||
type PubSubService struct {
|
||||
PS *pubsub.PubSub
|
||||
Subscription []string
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
var pubsubSingleton *PubSubService
|
||||
|
||||
func Init(ctx context.Context, ps *pubsub.PubSub) {
|
||||
pubsubSingleton = &PubSubService{
|
||||
PS: ps,
|
||||
Subscription: []string{},
|
||||
}
|
||||
pubsubSingleton.initSubscribeEvents(ctx)
|
||||
}
|
||||
|
||||
func GetPubSubService() *PubSubService {
|
||||
return pubsubSingleton
|
||||
}
|
||||
|
||||
func (ps *PubSubService) getTopicName(topicName string) tools.PubSubAction {
|
||||
ns := strings.Split(topicName, ".")
|
||||
if len(ns) > 0 {
|
||||
return tools.GetActionString(ns[0])
|
||||
}
|
||||
return tools.NONE
|
||||
}
|
||||
|
||||
func (abs *PubSubService) retrieveResponse(ctx context.Context, p *peer.Peer, event models.Event) error {
|
||||
res, err := resources.ToResource(int(event.DataType), event.Payload)
|
||||
if err != nil || res == nil {
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(res.Serialize(res))
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CATALOG_SEARCH_EVENT, tools.NATSResponse{
|
||||
FromApp: "oc-discovery",
|
||||
Datatype: tools.DataType(event.DataType),
|
||||
Method: int(tools.CATALOG_SEARCH_EVENT),
|
||||
Payload: b,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (abs *PubSubService) sendResponse(ctx context.Context, p *peer.Peer, event models.Event) error {
|
||||
dts := []oclib.LibDataEnum{oclib.LibDataEnum(event.DataType)}
|
||||
if event.DataType == -1 { // expect all resources
|
||||
dts = []oclib.LibDataEnum{oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), oclib.LibDataEnum(oclib.STORAGE_RESOURCE),
|
||||
oclib.LibDataEnum(oclib.PROCESSING_RESOURCE), oclib.LibDataEnum(oclib.DATA_RESOURCE), oclib.LibDataEnum(oclib.WORKFLOW_RESOURCE)}
|
||||
}
|
||||
var m map[string]string
|
||||
err := json.Unmarshal(event.Payload, &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dt := range dts {
|
||||
access := oclib.NewRequestAdmin(oclib.LibDataEnum(event.DataType), nil)
|
||||
peerID := p.GetID()
|
||||
searched := access.Search(abs.filterPeer(peerID, m["search"]), "", false)
|
||||
for _, ss := range searched.Data {
|
||||
if j, err := json.Marshal(ss); err == nil {
|
||||
if event.DataType != -1 {
|
||||
ndt := tools.DataType(dt.EnumIndex())
|
||||
abs.publishEvent(ctx, &ndt, tools.PB_SEARCH_RESPONSE, event.User, peerID, j, true)
|
||||
} else {
|
||||
abs.publishEvent(ctx, nil, tools.PB_SEARCH_RESPONSE, event.User, peerID, j, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (abs *PubSubService) filterPeer(peerID string, search string) *dbs.Filters {
|
||||
id, err := oclib.GetMySelf()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
filter := map[string][]dbs.Filter{
|
||||
"creator_id": {{Operator: dbs.EQUAL.String(), Value: id}}, // is my resource...
|
||||
"": {{Operator: dbs.OR.String(), Value: &dbs.Filters{
|
||||
Or: map[string][]dbs.Filter{
|
||||
"abstractobject.access_mode": {{Operator: dbs.EQUAL.String(), Value: 1}}, // if public
|
||||
"abstractinstanciatedresource.instances": {{Operator: dbs.ELEMMATCH.String(), Value: &dbs.Filters{ // or got a partners instances
|
||||
And: map[string][]dbs.Filter{
|
||||
"resourceinstance.partnerships": {{Operator: dbs.ELEMMATCH.String(), Value: &dbs.Filters{
|
||||
And: map[string][]dbs.Filter{
|
||||
"resourcepartnership.peer_groups." + peerID: {{Operator: dbs.EXISTS.String(), Value: true}},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}}},
|
||||
},
|
||||
}}},
|
||||
}
|
||||
if search != "" {
|
||||
filter[" "] = []dbs.Filter{{Operator: dbs.OR.String(), Value: &dbs.Filters{
|
||||
Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.type": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.owners.name": {{Operator: dbs.LIKE.String(), Value: search}},
|
||||
"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
|
||||
},
|
||||
}}}
|
||||
}
|
||||
return &dbs.Filters{
|
||||
And: filter,
|
||||
}
|
||||
}
|
||||
139
daemons/pubsub/subscribe.go
Normal file
139
daemons/pubsub/subscribe.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"oc-discovery/daemons"
|
||||
"oc-discovery/daemons/dht"
|
||||
"oc-discovery/models"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (ps *PubSubService) initSubscribeEvents(ctx context.Context) error {
|
||||
ourPeerID, err := oclib.GenerateNodeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ps.subscribeEvents(ctx, nil, tools.PB_SEARCH, "", -1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ps.subscribeEvents(ctx, nil, tools.PB_SEARCH, ourPeerID, -1); err != nil { // we subscribe at our proprer deductible search adresse.
|
||||
return err
|
||||
}
|
||||
if err := ps.initPartnersSubscribeEvents(ctx); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) initPartnersSubscribeEvents(ctx context.Context) error {
|
||||
// search all your partners : we check in base for this because we keep actively peer state if partners
|
||||
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
|
||||
peers := access.Search(nil, fmt.Sprintf("%v", peer.PARTNER.EnumIndex()), false)
|
||||
for _, p := range peers.Data {
|
||||
if err := ps.PartnerSubscribeEvents(ctx, p.(*peer.Peer).PeerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PubSubService) PartnerSubscribeEvents(ctx context.Context, myPartnerPeerID string) error {
|
||||
if myPartnerPeerID == "" {
|
||||
return errors.New("should discover a particular partner peer")
|
||||
}
|
||||
for _, action := range []tools.PubSubAction{tools.PB_CREATE, tools.PB_UPDATE, tools.PB_DELETE} {
|
||||
if err := ps.subscribeEvents(ctx, nil, action, myPartnerPeerID, -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 tools.PubSubAction, peerID string, timeout int,
|
||||
) error {
|
||||
// define a name app.action#peerID
|
||||
name := action.String() + "#" + peerID
|
||||
if dt != nil { // if a datatype is precised then : app.action.datatype#peerID
|
||||
name = 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 models.Event
|
||||
if err := json.Unmarshal(msg.Data, &evt); err != nil { // map to event
|
||||
continue
|
||||
}
|
||||
if p, err := dht.GetDHTService().GetPeer(ctx, evt.From); err == nil {
|
||||
if err := ps.processEventPeerKnown(ctx, p, evt, topicName); err != nil {
|
||||
logger.Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PubSubService) processEventPeerKnown(
|
||||
ctx context.Context, p *peer.Peer, event models.Event, topicName string) error {
|
||||
if err := daemons.VerifyPeer([]*peer.Peer{p}, event); err != nil {
|
||||
return err
|
||||
}
|
||||
return ps.handleEvent(ctx, topicName, event)
|
||||
}
|
||||
100
daemons/utils.go
Normal file
100
daemons/utils.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package daemons
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"oc-discovery/conf"
|
||||
"oc-discovery/models"
|
||||
"os"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/pnet"
|
||||
)
|
||||
|
||||
func VerifyPeer(peers []*peer.Peer, event models.Event) error {
|
||||
if len(peers) == 0 {
|
||||
return errors.New("no peer found")
|
||||
}
|
||||
p := peers[0]
|
||||
if p.Relation == peer.BLACKLIST { // if peer is blacklisted... quit...
|
||||
return errors.New("peer is blacklisted")
|
||||
}
|
||||
pubKey, err := PubKeyFromString(p.PublicKey) // extract pubkey from pubkey str
|
||||
if err != nil {
|
||||
return errors.New("pubkey is malformed")
|
||||
}
|
||||
data, err := event.ToRawByte()
|
||||
if err != nil {
|
||||
return err
|
||||
} // 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 Sign(priv crypto.PrivKey, data []byte) ([]byte, error) {
|
||||
return priv.Sign(data)
|
||||
}
|
||||
|
||||
func Verify(pub crypto.PubKey, data, sig []byte) (bool, error) {
|
||||
return pub.Verify(data, sig)
|
||||
}
|
||||
|
||||
func LoadKeyFromFile(isPublic bool) (crypto.PrivKey, error) {
|
||||
path := conf.GetConfig().PrivateKeyPath
|
||||
if isPublic {
|
||||
path = conf.GetConfig().PublicKeyPath
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try to unmarshal as libp2p private key (supports ed25519, rsa, etc.)
|
||||
priv, err := crypto.UnmarshalPrivateKey(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
func VerifyPubWithPriv() bool {
|
||||
priv, err := LoadKeyFromFile(false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
pub, err := LoadKeyFromFile(true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
return priv.GetPublic().Equals(pub)
|
||||
}
|
||||
|
||||
func LoadPSKFromFile() (pnet.PSK, error) {
|
||||
path := conf.GetConfig().PSKPath
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Try to unmarshal as libp2p private key (supports ed25519, rsa, etc.)
|
||||
psk, err := pnet.DecodeV1PSK(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return psk, nil
|
||||
}
|
||||
|
||||
func PubKeyFromString(s string) (crypto.PubKey, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return crypto.UnmarshalPublicKey(data)
|
||||
}
|
||||
Reference in New Issue
Block a user