IFM Architecture Documentation
Overview
IFM follows a layered architecture where each layer has a single responsibility. The protocol is designed to be transport-agnostic — the core logic doesn't know or care whether the underlying transport is QUIC, WebRTC, or TCP.
Interactive Viewer
Prefer exploring over reading? The interactive architecture viewer renders the whole system as an explorable graph — click components to zoom into their internals (audio pipeline, planes, mesh, relay pool) and see the spec/source links for each piece. It is the conceptual model; the live operational topology lives in the ifm-dashboard observability app.
Design Principles
1. No Central Servers
Every participant is a peer. The network continues operating even if thousands of peers disappear. There is no single point of failure.
2. Open Protocol
The protocol is public. Anyone can build clients, hardware, libraries, or extend the protocol. No patents, no vendor lock-in.
3. Portable
An IFM node runs anywhere: Raspberry Pi, laptop, phone, USB stick, IoT devices, browser (WebAssembly).
4. Stateless Communication
Joining a frequency requires no account, email, password, or login. Identity is cryptographic (Ed25519).
5. Radio Metaphor
Instead of Connect(server), users Tune(91.700). This mental model makes decentralized communication intuitive.
6. Station-Owned Content & Local Conversion
IFM is a real-time radio protocol. Stations own their source and content state. The protocol transports the station's real-time stream and associated data, while listeners receive the real-time stream and perform media decoding and conversion locally. Stations do not pre-convert audio or perform expensive per-listener transcoding, prioritizing fast path establishment and continuous low-latency delivery over file transfer or centralized processing.
Layer Responsibilities
Application Layer
User-facing applications built on the IFM SDK:
- Voice communication (walkie-talkie, conferencing)
- Text chat (channels, DMs, groups)
- Video streaming
- File transfer
- IoT sensor telemetry
- Multiplayer games
- AI agent communication
- Live streaming
SDK Layer (Language Bindings)
Provides ergonomic APIs for application developers:
- JavaScript/TypeScript:
@ifm/sdkfor Node, Bun, Browser - Rust: Native
ifm-corecrate - Future: Python, Go, Swift, Kotlin bindings
The SDK handles:
- Frequency management (tune, leave, scan)
- Packet encoding/decoding
- Event emission (message, voice, presence, peer events)
- Plugin registration
Core Layer (Rust)
The protocol engine — everything networking-related lives here:
| Crate | Responsibility |
|---|---|
ifm-protocol | Packet, Frequency, Header, Message Types, Serialization (pure, no networking) |
ifm-crypto | Ed25519, Noise, AES-GCM, ChaCha20, BLAKE3 (pure crypto) |
ifm-packet | Packet encoding/decoding, validation |
ifm-frequency | Frequency model, resolution, types (public/protected/hidden) |
ifm-discovery | Kademlia DHT, Bootstrap, Peer Discovery |
ifm-gossip | Packet cache, TTL, Duplicate removal, Forwarding, Bandwidth limits |
ifm-transport | QUIC, WebRTC, TCP fallback (transport abstraction) |
ifm-audio | Opus codec, Microphone capture, Playback, Streaming |
ifm-storage | Identity persistence, Config, Cache, Logs, Plugin storage |
ifm-node | High-level node orchestration, lifecycle management |
ifm-ffi | N-API bindings for Node/Bun, WASM bindings for browser |
Transport Layer (libp2p)
Battle-tested networking primitives:
- QUIC: Primary transport (low latency, multiplexed, encrypted)
- WebRTC: Browser compatibility, NAT traversal
- TCP: Fallback for restricted networks
- Kademlia: Distributed hash table for peer discovery
- GossipSub: Pub/sub mesh for broadcast
- Noise: Encrypted transport handshake
- Relay: Circuit relay for NAT traversal
- Hole Punching: Direct peer connections
Data Flow
Tuning to a Frequency
Broadcasting a Message
1. Application: radio.broadcast("Hello")
2. SDK: Create packet with payload_type=TEXT
3. Core: Assign sequence number, timestamp, TTL=16
4. Crypto: Sign packet with Ed25519 private key
5. Gossip: Add to local cache, forward to mesh via GossipSub
6. Transport: Send to connected peers
7. Peers: Verify signature, check TTL, deduplicate, relay
8. Recipients: SDK decodes, emits "message" eventVoice Streaming
Frequency Model
Human-Friendly vs Network Identifier
This separation allows:
- Rich naming without breaking compatibility
91.700,chat.general,weather.tokyo,sensor.greenhouse.3all map to fixed-size Topic IDs- Future namespaces without protocol changes
Frequency Types
| Type | Canonical Prefix | Encryption | Discovery |
|---|---|---|---|
| Public | ifm://public/ | None (plaintext) | Advertised in DHT |
| Protected | ifm://protected/ | ChaCha20-Poly1305 | Advertised, key required |
| Hidden | ifm://hidden/ | ChaCha20-Poly1305 | Not advertised, invite-only |
Node Architecture
Each device runs a Node with:
Identity Generation (Once)
Security Model
| Layer | Mechanism |
|---|---|
| Transport | Noise protocol (QUIC/WebRTC) — encrypted, authenticated channels |
| Packet | Every packet signed with Ed25519 — verified before relay |
| Frequency | Protected: ChaCha20-Poly1305 with shared key |
| Replay Protection | Packet ID (BLAKE3 hash) + sequence numbers |
| Duplicate Detection | Packet ID cache (LRU, configurable size) |
| Expiration | TTL = 16 hops, timestamps for freshness |
Performance Targets
| Metric | Target | Strategy |
|---|---|---|
| Startup | < 500ms | Lazy initialization, minimal bootstrap |
| Idle Memory | < 64 MB | Efficient data structures, bounded caches |
| Voice Latency | < 100ms | Opus 10ms real-time frames, direct peer paths, minimal hops |
| Peer Count | 100–500 | GossipSub fanout=6, Kademlia bucket sizing |
| Binary Size | < 30 MB | strip, lto, selective crate compilation |
Extension Points
Plugins
Everything is a plugin — the core only understands packets:
- VoicePlugin, ChatPlugin, VideoPlugin, FilePlugin
- TelemetryPlugin, AIPlugin, GamingPlugin
- Custom plugins via Rust or JavaScript
Transports
New transports implement the Transport trait — core is unaware.
Codecs
Opus for voice; extensible for video (AV1, VP9), custom codecs.
Deployment Topologies
Bootstrap Nodes
- Only introduce nodes to each other
- Never own or see traffic
- Anyone can run one
- Multiple for redundancy
Relay Nodes
Relays are station-agnostic, frequency-agnostic infrastructure in a shared pool. A relay does not care who connects or what it carries — it forwards any frequency it has seen to whoever asks, and none is ever attached to a station. This mirrors the demos' WebSocket relay hub: the hub registers itself in the pool (relay-hello, no station binding) and any number of stations transmit through it.
Node::serve_relay()— a node (station, listener, or dedicated process) joins the shared pool and forwards the frequencies it hears; it is never bound to any frequency or station.Node::stop_relay()leaves the pool.
Path choice — P2P-first, quality-driven (listener side). The relay is another path in the routing system, not a separate mode: the Path Manager (Node::best_path(), spec §19a) scores every candidate path — direct peers, alternate peers, and relays in the shared pool — continuously, from measured round-trip time, jitter, loss, bandwidth, hops, and stability, and the best-scoring path carries the media stream. This implements the Main Communication and Adaptive Forwarding Flow architecture rule: direct Station → Listener first, peer forwarding when it provides a better route, Relay only when it provides a superior path. Latency dominates the score, so:
- A healthy direct peer path (24 ms) stays P2P over a slower relay (55 ms).
- A degraded P2P path (
Station → A → B → Listenerat 80 ms / 4% loss / 35 ms jitter) migrates to a relay that measures clearly better (45 ms) — even while P2P still works. The relay repairs a section of the topology; only the problematic branch gets assistance. - When no relay is reachable or none scores better, the node transmits directly to peers; if nothing at all is reachable, it stays on its current connection — the mesh keeps running.
Migration is seamless: the alternate path is established while the current one still works and switched at a known media timestamp — good audio → good audio → good audio, never silence → reconnect → buffer.
Any listener extends coverage. Because serving is pool-wide, any listener already joined to a frequency can serve_relay() and become a forwarding hop for late-joining peers — the audience becomes part of the distribution infrastructure.
Circuit relay (NAT traversal). The libp2p transport additionally runs the circuit-relay v2 server + client behaviours: relay-capable nodes grant reservations and relay encrypted connections for peers behind NAT, so station → relay → peer holds even when the station or listeners are unreachable directly.
LAN Discovery
- UDP multicast for local peer discovery
- Zero-config local communication
- Falls back to DHT if no local peers