Anchor: add EXT-009 scoped presence query
Lets a client ask "is peer X currently online in network Y?" without joining that network first — a stateless O(1) lookup against the anchor's existing client registry. Scoped to (net, id) rather than a global lookup by id alone, so it can't be used as a cross-network "is this pubkey online anywhere" oracle: the caller must already know a network the peer belongs to, matching the knowledge already required to join it and observe presence the slow way via peer-join/peer-leave. Motivated by flit's known-devices list, where each pairing is its own isolated anchor network and the app holds no persistent connection while idle — today the only way to know if a device is reachable is to attempt a full connect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,8 +69,30 @@ type client struct {
|
||||
net string // hashed network name, set after join
|
||||
send chan proto.AnchorMessage
|
||||
conn *websocket.Conn
|
||||
|
||||
// EXT-009 presence_query rate limiting. Only ever touched from this
|
||||
// connection's own read-loop goroutine, so no lock needed.
|
||||
presenceQueryCount int
|
||||
presenceWindowStart time.Time
|
||||
}
|
||||
|
||||
// allowPresenceQuery implements a simple per-connection token bucket for
|
||||
// EXT-009 presence_query: presenceQueryLimit requests per presenceQueryWindow.
|
||||
func (c *client) allowPresenceQuery() bool {
|
||||
now := time.Now()
|
||||
if now.Sub(c.presenceWindowStart) > presenceQueryWindow {
|
||||
c.presenceWindowStart = now
|
||||
c.presenceQueryCount = 0
|
||||
}
|
||||
c.presenceQueryCount++
|
||||
return c.presenceQueryCount <= presenceQueryLimit
|
||||
}
|
||||
|
||||
const (
|
||||
presenceQueryLimit = 20
|
||||
presenceQueryWindow = 10 * time.Second
|
||||
)
|
||||
|
||||
type anchor struct {
|
||||
mu sync.RWMutex
|
||||
clients map[string]*client // keyed by hex peer id
|
||||
@@ -109,6 +131,16 @@ func (a *anchor) unregister(c *client) {
|
||||
log.Printf("anchor: peer left: %s", c.id[:min(8, len(c.id))])
|
||||
}
|
||||
|
||||
// isOnline reports whether a client is currently registered with the exact
|
||||
// (net, id) pair — EXT-009. O(1): reuses the existing global clients map,
|
||||
// no new state.
|
||||
func (a *anchor) isOnline(net, id string) bool {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
c, ok := a.clients[id]
|
||||
return ok && c.net == net
|
||||
}
|
||||
|
||||
// networkPeerIDs returns the hex ids of all peers in the same network as netHash.
|
||||
func (a *anchor) networkPeerIDs(netHash, excludeID string) []string {
|
||||
a.mu.RLock()
|
||||
@@ -225,6 +257,24 @@ func (a *anchor) handleWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
log.Printf("anchor: peer joined: %s net=%s peers=%d", c.id[:min(8, len(c.id))], msg.Net[:8], len(peers))
|
||||
|
||||
case proto.AnchorPresenceQuery:
|
||||
// EXT-009. Stateless and anchor-local: does not require the
|
||||
// querying connection to have joined any network. Scoped to
|
||||
// (net, id) — never a global "is this pubkey online anywhere"
|
||||
// lookup, to avoid a cross-network presence oracle.
|
||||
if msg.Net == "" || msg.ID == "" {
|
||||
continue
|
||||
}
|
||||
if !c.allowPresenceQuery() {
|
||||
continue
|
||||
}
|
||||
online := a.isOnline(msg.Net, msg.ID)
|
||||
resp := proto.AnchorMessage{Type: proto.AnchorPresence, Net: msg.Net, ID: msg.ID, Online: &online}
|
||||
select {
|
||||
case c.send <- resp:
|
||||
default:
|
||||
}
|
||||
|
||||
case proto.AnchorTo:
|
||||
if c.id == "" {
|
||||
continue // not joined yet
|
||||
|
||||
Reference in New Issue
Block a user