iop/apps/control-plane/internal/wire/edge_registry.go
toki 6f36449daa feat(control-plane): edge connector implementation and control proto updates
- Add edge connector module (edge.go, edge_registry.go, edge_server.go)
- Add edge server tests (edge_server_test.go, edge_test.go)
- Update control.proto with edge-related messages and RPCs
- Generate proto files for Go, Dart
- Update control-plane Dockerfile and configuration
- Add client-side proto bindings
- Update roadmap and milestones for control-plane-edge-wire-baseline
- Add agent-task documentation for edge connector and local smoke tests
- Update docker-compose.yml for edge support
2026-05-30 20:00:37 +09:00

96 lines
2.9 KiB
Go

package wire
import (
"sync"
"time"
proto_socket "git.toki-labs.com/toki/proto-socket/go"
iop "iop/proto/gen/iop"
)
// EdgeConnectionState captures the Control Plane's internal view of a single
// Edge wire connection. It is a registry foundation only; public status query
// and event relay APIs are deferred to later subtasks.
type EdgeConnectionState struct {
EdgeID string
EdgeName string
Version string
Capabilities []string
Protocol string
Connected bool
LastSeen time.Time
DisconnectInfo proto_socket.DisconnectInfo
// token identifies the specific connection that produced this state. It is an
// internal registry detail (not an external API contract) used to ignore a
// stale connection's late disconnect after the same edge_id reconnected.
token uint64
}
// EdgeRegistry stores Edge connection state keyed by edge_id behind a mutex.
type EdgeRegistry struct {
mu sync.Mutex
edges map[string]*EdgeConnectionState
nextToken uint64
}
// NewEdgeRegistry returns an empty Edge connection registry.
func NewEdgeRegistry() *EdgeRegistry {
return &EdgeRegistry{edges: make(map[string]*EdgeConnectionState)}
}
// MarkConnected records or refreshes an Edge as connected from its hello and
// returns the connection token to hand back on disconnect. A reconnection
// supersedes any earlier connection for the same edge_id.
func (r *EdgeRegistry) MarkConnected(req *iop.EdgeHelloRequest, at time.Time) uint64 {
r.mu.Lock()
defer r.mu.Unlock()
r.nextToken++
token := r.nextToken
r.edges[req.GetEdgeId()] = &EdgeConnectionState{
EdgeID: req.GetEdgeId(),
EdgeName: req.GetEdgeName(),
Version: req.GetVersion(),
Capabilities: append([]string(nil), req.GetCapabilities()...),
Protocol: Protocol,
Connected: true,
LastSeen: at,
token: token,
}
return token
}
// MarkDisconnected flips a tracked Edge to disconnected and records why, but
// only when token still matches the current connection. A stale token from a
// superseded connection is ignored so a reconnection is not cleared.
func (r *EdgeRegistry) MarkDisconnected(edgeID string, token uint64, at time.Time, info proto_socket.DisconnectInfo) {
r.mu.Lock()
defer r.mu.Unlock()
state, ok := r.edges[edgeID]
if !ok || state.token != token {
return
}
state.Connected = false
state.LastSeen = at
state.DisconnectInfo = info
}
// Snapshot returns a copy of the tracked state for an Edge.
func (r *EdgeRegistry) Snapshot(edgeID string) (EdgeConnectionState, bool) {
r.mu.Lock()
defer r.mu.Unlock()
state, ok := r.edges[edgeID]
if !ok {
return EdgeConnectionState{}, false
}
snapshot := *state
snapshot.Capabilities = append([]string(nil), state.Capabilities...)
return snapshot, true
}
// Len returns the number of tracked Edge connections.
func (r *EdgeRegistry) Len() int {
r.mu.Lock()
defer r.mu.Unlock()
return len(r.edges)
}