package indexer import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "oc-discovery/daemons/node/common" "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/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" ) type PeerRecord struct { Name string `json:"name"` DID string `json:"did"` // real PEER ID PeerID string `json:"peer_id"` PubKey []byte `json:"pub_key"` APIUrl string `json:"api_url"` StreamAddress string `json:"stream_address"` NATSAddress string `json:"nats_address"` WalletAddress string `json:"wallet_address"` Signature []byte `json:"signature"` ExpiryDate time.Time `json:"expiry_date"` } func (p *PeerRecord) Sign() error { priv, err := tools.LoadKeyFromFilePrivate() if err != nil { return err } dht := PeerRecord{ Name: p.Name, DID: p.DID, PubKey: p.PubKey, ExpiryDate: p.ExpiryDate, } payload, _ := json.Marshal(dht) b, err := common.Sign(priv, payload) p.Signature = b return err } func (p *PeerRecord) Verify() (crypto.PubKey, error) { pubKey, err := crypto.UnmarshalPublicKey(p.PubKey) // retrieve pub key in message if err != nil { fmt.Println("UnmarshalPublicKey") return pubKey, err } dht := PeerRecord{ Name: p.Name, DID: p.DID, PubKey: p.PubKey, ExpiryDate: p.ExpiryDate, } payload, _ := json.Marshal(dht) if ok, _ := common.Verify(pubKey, payload, p.Signature); !ok { // verify minimal message was sign per pubKey fmt.Println("Verify") return pubKey, errors.New("invalid signature") } return pubKey, nil } func (pr *PeerRecord) ExtractPeer(ourkey string, key string, pubKey crypto.PubKey) (bool, *pp.Peer, error) { pubBytes, err := crypto.MarshalPublicKey(pubKey) if err != nil { return false, nil, err } rel := pp.NONE if ourkey == key { // at this point is PeerID is same as our... we are... thats our peer INFO rel = pp.SELF } p := &pp.Peer{ AbstractObject: utils.AbstractObject{ UUID: pr.DID, Name: pr.Name, }, Relation: rel, // VERIFY.... it crush nothing PeerID: pr.PeerID, PublicKey: base64.StdEncoding.EncodeToString(pubBytes), APIUrl: pr.APIUrl, StreamAddress: pr.StreamAddress, NATSAddress: pr.NATSAddress, WalletAddress: pr.WalletAddress, } b, err := json.Marshal(p) if err != nil { return pp.SELF == p.Relation, nil, err } if time.Now().UTC().After(pr.ExpiryDate) { return pp.SELF == p.Relation, nil, errors.New("peer " + key + " is offline") } go tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{ FromApp: "oc-discovery", Datatype: tools.PEER, Method: int(tools.CREATE_RESOURCE), SearchAttr: "peer_id", Payload: b, }) return pp.SELF == p.Relation, p, nil } type GetValue struct { Key string `json:"key"` PeerID peer.ID `json:"peer_id"` } type GetResponse struct { Found bool `json:"found"` Records map[string]PeerRecord `json:"records,omitempty"` } func (ix *IndexerService) genKey(did string) string { return "/node/" + did } func (ix *IndexerService) initNodeHandler() { ix.Host.SetStreamHandler(common.ProtocolHeartbeat, ix.HandleNodeHeartbeat) ix.Host.SetStreamHandler(common.ProtocolPublish, ix.handleNodePublish) ix.Host.SetStreamHandler(common.ProtocolGet, ix.handleNodeGet) } func (ix *IndexerService) handleNodePublish(s network.Stream) { defer s.Close() logger := oclib.GetLogger() for { var rec PeerRecord if err := json.NewDecoder(s).Decode(&rec); err != nil { logger.Err(err) continue } rec2 := PeerRecord{ Name: rec.Name, DID: rec.DID, // REAL PEER ID PubKey: rec.PubKey, PeerID: rec.PeerID, } if _, err := rec2.Verify(); err != nil { logger.Err(err) continue } if rec.PeerID == "" || rec.ExpiryDate.Before(time.Now().UTC()) { // already expired logger.Err(errors.New(rec.PeerID + " is expired.")) continue } pid, err := peer.Decode(rec.PeerID) if err != nil { continue } ix.StreamMU.Lock() if ix.StreamRecords[common.ProtocolHeartbeat] == nil { ix.StreamRecords[common.ProtocolHeartbeat] = map[peer.ID]*common.StreamRecord[PeerRecord]{} } streams := ix.StreamRecords[common.ProtocolHeartbeat] if srec, ok := streams[pid]; ok { srec.DID = rec.DID srec.Record = rec srec.HeartbeatStream.UptimeTracker.LastSeen = time.Now().UTC() } else { ix.StreamMU.Unlock() logger.Err(errors.New("no heartbeat")) continue } ix.StreamMU.Unlock() key := ix.genKey(rec.DID) data, err := json.Marshal(rec) if err != nil { logger.Err(err) continue } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) if err := ix.DHT.PutValue(ctx, key, data); err != nil { logger.Err(err) cancel() continue } cancel() break // response... so quit } } func (ix *IndexerService) handleNodeGet(s network.Stream) { defer s.Close() logger := oclib.GetLogger() for { var req GetValue if err := json.NewDecoder(s).Decode(&req); err != nil { logger.Err(err) continue } ix.StreamMU.Lock() if ix.StreamRecords[common.ProtocolHeartbeat] == nil { ix.StreamRecords[common.ProtocolHeartbeat] = map[peer.ID]*common.StreamRecord[PeerRecord]{} } resp := GetResponse{ Found: false, Records: map[string]PeerRecord{}, } streams := ix.StreamRecords[common.ProtocolHeartbeat] key := ix.genKey(req.Key) // simple lookup by PeerID (or DID) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) recBytes, err := ix.DHT.SearchValue(ctx, key) if err != nil { logger.Err(err).Msg("Failed to fetch PeerRecord from DHT") cancel() } cancel() for c := range recBytes { var rec PeerRecord if err := json.Unmarshal(c, &rec); err != nil || rec.PeerID != req.PeerID.String() { continue } resp.Found = true resp.Records[rec.PeerID] = rec if srec, ok := streams[req.PeerID]; ok { srec.DID = rec.DID srec.Record = rec srec.HeartbeatStream.UptimeTracker.LastSeen = time.Now().UTC() } } // Not found _ = json.NewEncoder(s).Encode(resp) ix.StreamMU.Unlock() break // response... so quit } }