Skip to content

jmerelnyc/featherweight

Repository files navigation

featherweight

Minimal implementation of Lightweight protocol for resource-constrained environments

motivation

Most Lightweight protocol implementations target server and desktop environments, bringing dependencies and memory footprints that don't fit embedded systems. Microcontrollers with 64KB RAM and single-core processors can't run a multi-megabyte binary that assumes threading and dynamic allocation.

featherweight strips the protocol down to its core. No async runtime, no heap allocations in the hot path, and a binary size under 50KB. It implements the subset of Lightweight that IoT devices actually need: basic pub/sub, QoS 0 and 1, and session persistence.

architecture

graph TD
    A[Application] --> B[featherweight Client]
    B --> C[Packet Encoder/Decoder]
    C --> D[State Machine]
    D --> E[Transport Layer]
    E --> F[TCP/UDP Socket]
    
    G[Static Buffer Pool] -.-> C
    G -.-> D
    
    H[Session Store] -.-> D
    
    style G fill:#f9f,stroke:#333
    style H fill:#f9f,stroke:#333
Loading

getting started

install

cargo add featherweight

quickstart

use featherweight::{Client, Config, QoS};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config {
        client_id: "sensor_01",
        keep_alive: 60,
        ..Default::default()
    };
    
    let mut client = Client::new(config)?;
    client.connect("192.168.1.100:1883")?;
    
    // Subscribe to topic
    client.subscribe("sensors/temperature", QoS::AtLeastOnce)?;
    
    // Publish data
    let payload = b"22.5";
    client.publish("sensors/temp/living_room", payload, QoS::AtMostOnce)?;
    
    // Process incoming messages
    loop {
        if let Some(msg) = client.poll()? {
            println!("Topic: {}, Payload: {:?}", msg.topic, msg.payload);
        }
    }
}

how it works

The client uses a fixed-size buffer pool allocated at startup. When encoding packets, it pulls buffers from the pool, writes the protocol frames, and returns them after transmission. This avoids fragmentation and makes memory usage predictable.

The state machine tracks connection state, pending acknowledgments, and retry timers without allocating. Each QoS 1 message gets a slot in a fixed array of in-flight messages. When the array fills, publish operations return an error until slots free up.

Session state (client ID, subscriptions, pending messages) serializes to a byte array that you can write to flash or EEPROM. On restart, deserialize and pass it to the client to resume where you left off.

The transport layer abstracts over TCP and UDP through a simple Read/Write trait. Bring your own socket implementation or use the provided std adapter. For no_std targets, implement the trait over your platform's network stack.

configuration

Config {
    // Required
    client_id: &str,
    
    // Connection
    keep_alive: u16,           // Seconds between pings (default: 60)
    connect_timeout: u32,      // Connection timeout in ms (default: 5000)
    
    // Buffer sizing
    max_packet_size: usize,    // Maximum packet size (default: 1024)
    buffer_pool_size: usize,   // Number of buffers (default: 4)
    max_inflight: usize,       // Max QoS 1 messages in flight (default: 8)
    
    // Optional authentication
    username: Option<&str>,
    password: Option<&[u8]>,
    
    // Session
    clean_session: bool,       // Start fresh or resume (default: true)
}

faq

Q: Why not just use rumqtt or paho?

A: Those are great for applications with an OS and plenty of resources. featherweight targets Cortex-M microcontrollers where you measure RAM in kilobytes and can't afford heap fragmentation.

Q: What parts of the protocol are missing?

A: QoS 2 (exactly once delivery), wildcards in subscriptions, last will and testament, retained messages. Add them if you need them, but most sensors don't.

Q: Does it work with no_std?

A: Yes. Disable default features and implement the Transport trait for your network stack. See examples/no_std for a sample integration.

Q: How do I handle errors?

A: Operations return Result types. Network errors and protocol violations are distinct error types. The client stays in a valid state after errors, you decide whether to retry or reconnect.

Q: Can I run multiple clients in one program?

A: Yes, Client is not a singleton. Each instance manages its own connection and buffer pool.

license

MIT OR Apache-2.0

About

Minimal implementation of Lightweight protocol for resource-constrained environments

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors