package indexer import ( "encoding/json" "errors" "time" ) type DefaultValidator struct{} func (v DefaultValidator) Validate(key string, value []byte) error { return nil } func (v DefaultValidator) Select(key string, values [][]byte) (int, error) { return 0, nil } type PeerRecordValidator struct{} func (v PeerRecordValidator) Validate(key string, value []byte) error { // Accept valid tombstones — deletion must be storable so it can propagate // and win over stale live records on other DHT nodes via Select(). var ts TombstoneRecord if err := json.Unmarshal(value, &ts); err == nil && ts.Tombstone { if ts.PeerID == "" || ts.DID == "" { return errors.New("tombstone: missing fields") } if time.Since(ts.DeletedAt) > tombstoneTTL { return errors.New("tombstone: expired") } if _, err := ts.Verify(); err != nil { return errors.New("tombstone: " + err.Error()) } return nil } var rec PeerRecord if err := json.Unmarshal(value, &rec); err != nil { return errors.New("invalid json") } // PeerID must exist if rec.PeerID == "" { return errors.New("missing peerID") } // Expiry check if rec.ExpiryDate.Before(time.Now().UTC()) { return errors.New("record expired") } // TTL cap: publisher cannot set an expiry further than maxTTLSeconds in // the future. Prevents abuse (e.g. records designed to linger for years). if rec.ExpiryDate.After(time.Now().UTC().Add(maxTTLSeconds * time.Second)) { return errors.New("TTL exceeds maximum allowed") } // Signature verification if _, err := rec.Verify(); err != nil { return errors.New("invalid signature") } return nil } func (v PeerRecordValidator) Select(key string, values [][]byte) (int, error) { // Tombstone always wins: a signed delete supersedes any live record, // even if the live record has a later ExpiryDate. for i, val := range values { var ts TombstoneRecord if err := json.Unmarshal(val, &ts); err == nil && ts.Tombstone { return i, nil } } var newest time.Time index := 0 for i, val := range values { var rec PeerRecord if err := json.Unmarshal(val, &rec); err != nil { continue } if rec.ExpiryDate.After(newest) { newest = rec.ExpiryDate index = i } } return index, nil }