Skip to content

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/sdk for Node, Bun, Browser
  • Rust: Native ifm-core crate
  • 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:

CrateResponsibility
ifm-protocolPacket, Frequency, Header, Message Types, Serialization (pure, no networking)
ifm-cryptoEd25519, Noise, AES-GCM, ChaCha20, BLAKE3 (pure crypto)
ifm-packetPacket encoding/decoding, validation
ifm-frequencyFrequency model, resolution, types (public/protected/hidden)
ifm-discoveryKademlia DHT, Bootstrap, Peer Discovery
ifm-gossipPacket cache, TTL, Duplicate removal, Forwarding, Bandwidth limits
ifm-transportQUIC, WebRTC, TCP fallback (transport abstraction)
ifm-audioOpus codec, Microphone capture, Playback, Streaming
ifm-storageIdentity persistence, Config, Cache, Logs, Plugin storage
ifm-nodeHigh-level node orchestration, lifecycle management
ifm-ffiN-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" event

Voice Streaming ​


Frequency Model ​

Human-Friendly vs Network Identifier ​

This separation allows:

  • Rich naming without breaking compatibility
  • 91.700, chat.general, weather.tokyo, sensor.greenhouse.3 all map to fixed-size Topic IDs
  • Future namespaces without protocol changes

Frequency Types ​

TypeCanonical PrefixEncryptionDiscovery
Publicifm://public/None (plaintext)Advertised in DHT
Protectedifm://protected/ChaCha20-Poly1305Advertised, key required
Hiddenifm://hidden/ChaCha20-Poly1305Not advertised, invite-only

Node Architecture ​

Each device runs a Node with:

Identity Generation (Once) ​


Security Model ​

LayerMechanism
TransportNoise protocol (QUIC/WebRTC) — encrypted, authenticated channels
PacketEvery packet signed with Ed25519 — verified before relay
FrequencyProtected: ChaCha20-Poly1305 with shared key
Replay ProtectionPacket ID (BLAKE3 hash) + sequence numbers
Duplicate DetectionPacket ID cache (LRU, configurable size)
ExpirationTTL = 16 hops, timestamps for freshness

Performance Targets ​

MetricTargetStrategy
Startup< 500msLazy initialization, minimal bootstrap
Idle Memory< 64 MBEfficient data structures, bounded caches
Voice Latency< 100msOpus 10ms real-time frames, direct peer paths, minimal hops
Peer Count100–500GossipSub fanout=6, Kademlia bucket sizing
Binary Size< 30 MBstrip, 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 → Listener at 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

Mermaid Diagram: Full Architecture ​

Released under the MIT License.