implement messages and operations for FIND
This commit is contained in:
455
pkg/find/parser.go
Normal file
455
pkg/find/parser.go
Normal file
@@ -0,0 +1,455 @@
|
||||
package find
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/encoders/tag"
|
||||
)
|
||||
|
||||
// getTagValue retrieves the value of the first tag with the given key
|
||||
func getTagValue(ev *event.E, key string) string {
|
||||
t := ev.Tags.GetFirst([]byte(key))
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return string(t.Value())
|
||||
}
|
||||
|
||||
// getAllTags retrieves all tags with the given key
|
||||
func getAllTags(ev *event.E, key string) []*tag.T {
|
||||
return ev.Tags.GetAll([]byte(key))
|
||||
}
|
||||
|
||||
// ParseRegistrationProposal parses a kind 30100 event into a RegistrationProposal
|
||||
func ParseRegistrationProposal(ev *event.E) (*RegistrationProposal, error) {
|
||||
if uint16(ev.Kind) != KindRegistrationProposal {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindRegistrationProposal, ev.Kind)
|
||||
}
|
||||
|
||||
name := getTagValue(ev, "d")
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("missing 'd' tag (name)")
|
||||
}
|
||||
|
||||
action := getTagValue(ev, "action")
|
||||
if action == "" {
|
||||
return nil, fmt.Errorf("missing 'action' tag")
|
||||
}
|
||||
|
||||
expirationStr := getTagValue(ev, "expiration")
|
||||
var expiration time.Time
|
||||
if expirationStr != "" {
|
||||
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expiration timestamp: %w", err)
|
||||
}
|
||||
expiration = time.Unix(expirationUnix, 0)
|
||||
}
|
||||
|
||||
proposal := &RegistrationProposal{
|
||||
Event: ev,
|
||||
Name: name,
|
||||
Action: action,
|
||||
PrevOwner: getTagValue(ev, "prev_owner"),
|
||||
PrevSig: getTagValue(ev, "prev_sig"),
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
return proposal, nil
|
||||
}
|
||||
|
||||
// ParseAttestation parses a kind 20100 event into an Attestation
|
||||
func ParseAttestation(ev *event.E) (*Attestation, error) {
|
||||
if uint16(ev.Kind) != KindAttestation {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindAttestation, ev.Kind)
|
||||
}
|
||||
|
||||
proposalID := getTagValue(ev, "e")
|
||||
if proposalID == "" {
|
||||
return nil, fmt.Errorf("missing 'e' tag (proposal ID)")
|
||||
}
|
||||
|
||||
decision := getTagValue(ev, "decision")
|
||||
if decision == "" {
|
||||
return nil, fmt.Errorf("missing 'decision' tag")
|
||||
}
|
||||
|
||||
weightStr := getTagValue(ev, "weight")
|
||||
weight := 100 // default weight
|
||||
if weightStr != "" {
|
||||
w, err := strconv.Atoi(weightStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid weight value: %w", err)
|
||||
}
|
||||
weight = w
|
||||
}
|
||||
|
||||
expirationStr := getTagValue(ev, "expiration")
|
||||
var expiration time.Time
|
||||
if expirationStr != "" {
|
||||
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expiration timestamp: %w", err)
|
||||
}
|
||||
expiration = time.Unix(expirationUnix, 0)
|
||||
}
|
||||
|
||||
attestation := &Attestation{
|
||||
Event: ev,
|
||||
ProposalID: proposalID,
|
||||
Decision: decision,
|
||||
Weight: weight,
|
||||
Reason: getTagValue(ev, "reason"),
|
||||
ServiceURL: getTagValue(ev, "service"),
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
// ParseTrustGraph parses a kind 30101 event into a TrustGraph
|
||||
func ParseTrustGraph(ev *event.E) (*TrustGraph, error) {
|
||||
if uint16(ev.Kind) != KindTrustGraph {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindTrustGraph, ev.Kind)
|
||||
}
|
||||
|
||||
expirationStr := getTagValue(ev, "expiration")
|
||||
var expiration time.Time
|
||||
if expirationStr != "" {
|
||||
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expiration timestamp: %w", err)
|
||||
}
|
||||
expiration = time.Unix(expirationUnix, 0)
|
||||
}
|
||||
|
||||
// Parse p tags (trust entries)
|
||||
var entries []TrustEntry
|
||||
pTags := getAllTags(ev, "p")
|
||||
for _, t := range pTags {
|
||||
if len(t.T) < 2 {
|
||||
continue // Skip malformed tags
|
||||
}
|
||||
|
||||
pubkey := string(t.T[1])
|
||||
serviceURL := ""
|
||||
trustScore := 0.5 // default
|
||||
|
||||
if len(t.T) > 2 {
|
||||
serviceURL = string(t.T[2])
|
||||
}
|
||||
|
||||
if len(t.T) > 3 {
|
||||
score, err := strconv.ParseFloat(string(t.T[3]), 64)
|
||||
if err == nil {
|
||||
trustScore = score
|
||||
}
|
||||
}
|
||||
|
||||
entries = append(entries, TrustEntry{
|
||||
Pubkey: pubkey,
|
||||
ServiceURL: serviceURL,
|
||||
TrustScore: trustScore,
|
||||
})
|
||||
}
|
||||
|
||||
return &TrustGraph{
|
||||
Event: ev,
|
||||
Entries: entries,
|
||||
Expiration: expiration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseNameState parses a kind 30102 event into a NameState
|
||||
func ParseNameState(ev *event.E) (*NameState, error) {
|
||||
if uint16(ev.Kind) != KindNameState {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindNameState, ev.Kind)
|
||||
}
|
||||
|
||||
name := getTagValue(ev, "d")
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("missing 'd' tag (name)")
|
||||
}
|
||||
|
||||
owner := getTagValue(ev, "owner")
|
||||
if owner == "" {
|
||||
return nil, fmt.Errorf("missing 'owner' tag")
|
||||
}
|
||||
|
||||
registeredAtStr := getTagValue(ev, "registered_at")
|
||||
if registeredAtStr == "" {
|
||||
return nil, fmt.Errorf("missing 'registered_at' tag")
|
||||
}
|
||||
registeredAtUnix, err := strconv.ParseInt(registeredAtStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid registered_at timestamp: %w", err)
|
||||
}
|
||||
registeredAt := time.Unix(registeredAtUnix, 0)
|
||||
|
||||
attestationsStr := getTagValue(ev, "attestations")
|
||||
attestations := 0
|
||||
if attestationsStr != "" {
|
||||
a, err := strconv.Atoi(attestationsStr)
|
||||
if err == nil {
|
||||
attestations = a
|
||||
}
|
||||
}
|
||||
|
||||
confidenceStr := getTagValue(ev, "confidence")
|
||||
confidence := 0.0
|
||||
if confidenceStr != "" {
|
||||
c, err := strconv.ParseFloat(confidenceStr, 64)
|
||||
if err == nil {
|
||||
confidence = c
|
||||
}
|
||||
}
|
||||
|
||||
expirationStr := getTagValue(ev, "expiration")
|
||||
var expiration time.Time
|
||||
if expirationStr != "" {
|
||||
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expiration timestamp: %w", err)
|
||||
}
|
||||
expiration = time.Unix(expirationUnix, 0)
|
||||
}
|
||||
|
||||
return &NameState{
|
||||
Event: ev,
|
||||
Name: name,
|
||||
Owner: owner,
|
||||
RegisteredAt: registeredAt,
|
||||
ProposalID: getTagValue(ev, "proposal"),
|
||||
Attestations: attestations,
|
||||
Confidence: confidence,
|
||||
Expiration: expiration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseNameRecord parses a kind 30103 event into a NameRecord
|
||||
func ParseNameRecord(ev *event.E) (*NameRecord, error) {
|
||||
if uint16(ev.Kind) != KindNameRecords {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindNameRecords, ev.Kind)
|
||||
}
|
||||
|
||||
name := getTagValue(ev, "name")
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("missing 'name' tag")
|
||||
}
|
||||
|
||||
recordType := getTagValue(ev, "type")
|
||||
if recordType == "" {
|
||||
return nil, fmt.Errorf("missing 'type' tag")
|
||||
}
|
||||
|
||||
value := getTagValue(ev, "value")
|
||||
if value == "" {
|
||||
return nil, fmt.Errorf("missing 'value' tag")
|
||||
}
|
||||
|
||||
ttlStr := getTagValue(ev, "ttl")
|
||||
ttl := 3600 // default TTL
|
||||
if ttlStr != "" {
|
||||
t, err := strconv.Atoi(ttlStr)
|
||||
if err == nil {
|
||||
ttl = t
|
||||
}
|
||||
}
|
||||
|
||||
priorityStr := getTagValue(ev, "priority")
|
||||
priority := 0
|
||||
if priorityStr != "" {
|
||||
p, err := strconv.Atoi(priorityStr)
|
||||
if err == nil {
|
||||
priority = p
|
||||
}
|
||||
}
|
||||
|
||||
weightStr := getTagValue(ev, "weight")
|
||||
weight := 0
|
||||
if weightStr != "" {
|
||||
w, err := strconv.Atoi(weightStr)
|
||||
if err == nil {
|
||||
weight = w
|
||||
}
|
||||
}
|
||||
|
||||
portStr := getTagValue(ev, "port")
|
||||
port := 0
|
||||
if portStr != "" {
|
||||
p, err := strconv.Atoi(portStr)
|
||||
if err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
|
||||
return &NameRecord{
|
||||
Event: ev,
|
||||
Name: name,
|
||||
Type: recordType,
|
||||
Value: value,
|
||||
TTL: ttl,
|
||||
Priority: priority,
|
||||
Weight: weight,
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseCertificate parses a kind 30104 event into a Certificate
|
||||
func ParseCertificate(ev *event.E) (*Certificate, error) {
|
||||
if uint16(ev.Kind) != KindCertificate {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindCertificate, ev.Kind)
|
||||
}
|
||||
|
||||
name := getTagValue(ev, "name")
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("missing 'name' tag")
|
||||
}
|
||||
|
||||
certPubkey := getTagValue(ev, "cert_pubkey")
|
||||
if certPubkey == "" {
|
||||
return nil, fmt.Errorf("missing 'cert_pubkey' tag")
|
||||
}
|
||||
|
||||
validFromStr := getTagValue(ev, "valid_from")
|
||||
if validFromStr == "" {
|
||||
return nil, fmt.Errorf("missing 'valid_from' tag")
|
||||
}
|
||||
validFromUnix, err := strconv.ParseInt(validFromStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid valid_from timestamp: %w", err)
|
||||
}
|
||||
validFrom := time.Unix(validFromUnix, 0)
|
||||
|
||||
validUntilStr := getTagValue(ev, "valid_until")
|
||||
if validUntilStr == "" {
|
||||
return nil, fmt.Errorf("missing 'valid_until' tag")
|
||||
}
|
||||
validUntilUnix, err := strconv.ParseInt(validUntilStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid valid_until timestamp: %w", err)
|
||||
}
|
||||
validUntil := time.Unix(validUntilUnix, 0)
|
||||
|
||||
// Parse witness tags
|
||||
var witnesses []WitnessSignature
|
||||
witnessTags := getAllTags(ev, "witness")
|
||||
for _, t := range witnessTags {
|
||||
if len(t.T) < 3 {
|
||||
continue // Skip malformed tags
|
||||
}
|
||||
|
||||
witnesses = append(witnesses, WitnessSignature{
|
||||
Pubkey: string(t.T[1]),
|
||||
Signature: string(t.T[2]),
|
||||
})
|
||||
}
|
||||
|
||||
// Parse content JSON
|
||||
algorithm := "secp256k1-schnorr"
|
||||
usage := "tls-replacement"
|
||||
if len(ev.Content) > 0 {
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal(ev.Content, &metadata); err == nil {
|
||||
if alg, ok := metadata["algorithm"].(string); ok {
|
||||
algorithm = alg
|
||||
}
|
||||
if u, ok := metadata["usage"].(string); ok {
|
||||
usage = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Certificate{
|
||||
Event: ev,
|
||||
Name: name,
|
||||
CertPubkey: certPubkey,
|
||||
ValidFrom: validFrom,
|
||||
ValidUntil: validUntil,
|
||||
Challenge: getTagValue(ev, "challenge"),
|
||||
ChallengeProof: getTagValue(ev, "challenge_proof"),
|
||||
Witnesses: witnesses,
|
||||
Algorithm: algorithm,
|
||||
Usage: usage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseWitnessService parses a kind 30105 event into a WitnessService
|
||||
func ParseWitnessService(ev *event.E) (*WitnessService, error) {
|
||||
if uint16(ev.Kind) != KindWitnessService {
|
||||
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindWitnessService, ev.Kind)
|
||||
}
|
||||
|
||||
endpoint := getTagValue(ev, "endpoint")
|
||||
if endpoint == "" {
|
||||
return nil, fmt.Errorf("missing 'endpoint' tag")
|
||||
}
|
||||
|
||||
// Parse challenge tags
|
||||
var challenges []string
|
||||
challengeTags := getAllTags(ev, "challenges")
|
||||
for _, t := range challengeTags {
|
||||
if len(t.T) >= 2 {
|
||||
challenges = append(challenges, string(t.T[1]))
|
||||
}
|
||||
}
|
||||
|
||||
maxValidityStr := getTagValue(ev, "max_validity")
|
||||
maxValidity := 0
|
||||
if maxValidityStr != "" {
|
||||
mv, err := strconv.Atoi(maxValidityStr)
|
||||
if err == nil {
|
||||
maxValidity = mv
|
||||
}
|
||||
}
|
||||
|
||||
feeStr := getTagValue(ev, "fee")
|
||||
fee := 0
|
||||
if feeStr != "" {
|
||||
f, err := strconv.Atoi(feeStr)
|
||||
if err == nil {
|
||||
fee = f
|
||||
}
|
||||
}
|
||||
|
||||
expirationStr := getTagValue(ev, "expiration")
|
||||
var expiration time.Time
|
||||
if expirationStr != "" {
|
||||
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expiration timestamp: %w", err)
|
||||
}
|
||||
expiration = time.Unix(expirationUnix, 0)
|
||||
}
|
||||
|
||||
// Parse content JSON
|
||||
description := ""
|
||||
contact := ""
|
||||
if len(ev.Content) > 0 {
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal(ev.Content, &metadata); err == nil {
|
||||
if desc, ok := metadata["description"].(string); ok {
|
||||
description = desc
|
||||
}
|
||||
if cont, ok := metadata["contact"].(string); ok {
|
||||
contact = cont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &WitnessService{
|
||||
Event: ev,
|
||||
Endpoint: endpoint,
|
||||
Challenges: challenges,
|
||||
MaxValidity: maxValidity,
|
||||
Fee: fee,
|
||||
ReputationID: getTagValue(ev, "reputation"),
|
||||
Description: description,
|
||||
Contact: contact,
|
||||
Expiration: expiration,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user