94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
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
|
|
}
|