// Package federationapi is the Server-Server API as a running component: // the inbound endpoints other servers call, the outbound queue that sends // them this server's events, and the Module that starts and stops it all // with the federation switch. // // The protocol itself — X-Matrix signing, server discovery, the HTTP // client, keys — is in package federation. This package is where that // protocol meets the roomserver and the sync API, which is why it is // separate: the roomserver uses the protocol, and this package uses the // roomserver. package federationapi import ( "context" "log/slog" "sync" ) // Module is the switchable part of federation: everything that runs in the // background and talks to other servers on its own initiative. // // The inbound endpoints are not in it. They are behind the gate, which // reads the switch on every request, so they need nothing started or // stopped. What does is the outbound queue — the only thing in Warp that // opens connections to other servers unprompted — and turning federation // off stops it mid-flight, leaving what it had not sent in the database for // when federation comes back on. // // Requests a component makes on a client's behalf — a join, a key fetch, // a profile lookup — need nothing stopping either: the client refuses // every request while the switch is off. type Module struct { queue *Queue log *slog.Logger mu sync.Mutex running bool } // NewModule builds a stopped module. func NewModule(queue *Queue, log *slog.Logger) *Module { if log == nil { log = slog.Default() } return &Module{queue: queue, log: log} } // Start brings federation up. Starting a running module does nothing. func (m *Module) Start(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() if m.running { return nil } if err := m.queue.Start(ctx); err != nil { m.queue.Stop() return err } m.running = true m.log.Info("federation started") return nil } // Stop takes federation down and waits for in-flight sends to finish or // be cancelled. Stopping a stopped module does nothing. func (m *Module) Stop() { m.mu.Lock() defer m.mu.Unlock() if !m.running { return } m.queue.Stop() m.running = false m.log.Info("federation stopped") } // Apply starts or stops the module to match the switch. func (m *Module) Apply(ctx context.Context, enabled bool) { if enabled { if err := m.Start(ctx); err != nil { m.log.Error("could not start federation", "error", err) } return } m.Stop() } // Running reports whether the module is up. func (m *Module) Running() bool { m.mu.Lock() defer m.mu.Unlock() return m.running } // Queue exposes the outbound queue, for the admin API. func (m *Module) Queue() *Queue { return m.queue }