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.