| Crates.io | sood |
| lib.rs | sood |
| version | 0.1.0 |
| created_at | 2026-01-16 03:52:58.252576+00 |
| updated_at | 2026-01-16 03:52:58.252576+00 |
| description | A Rust implementation of the Sood discovery protocol used by Roon |
| homepage | |
| repository | https://github.com/yourusername/sood |
| max_upload_size | |
| id | 2047814 |
| size | 85,991 |
A Rust implementation of the SOOD discovery protocol used by Roon for discovering the Core as well as audio endpoints on the local network.
SOOD is a UDP-based service discovery protocol that uses multicast and broadcast to discover devices on the local network. This crate provides a simple async API for sending discovery queries and receiving responses.
Add this to your Cargo.toml:
[dependencies]
sood = "0.1"
tokio = { version = "1.0", features = ["full"] }
The library provides constants for common Roon service IDs:
service_ids::ROON_API - Roon API service (used by extensions and control applications)service_ids::ROON_SERVER - Roon Core serverservice_ids::ROON_OS - Roon OS/Nucleus devicesYou can use these instead of hardcoding UUIDs:
use sood::{service_ids, Sood};
// Discover Roon API services
sood.discover_service(service_ids::ROON_API).await?;
// Discover Roon Core servers
sood.discover_service(service_ids::ROON_SERVER).await?;
// Discover Roon OS devices
sood.discover_service(service_ids::ROON_OS).await?;
use sood::{service_ids, Sood};
#[tokio::main]
async fn main() -> std::io::Result<()> {
// Create and start the discovery client
let mut sood = Sood::new()?;
sood.start().await?;
// Get a receiver for discovered devices (includes past discoveries)
let mut devices = sood.discovered().await;
// Send a discovery query for Roon API services
sood.discover_service(service_ids::ROON_API).await?;
// Listen for discovered devices (automatically filtered and deduplicated)
while let Ok(device) = devices.recv().await {
println!("Discovered: {} at {}", device.unique_id, device.from);
if let Some(Some(port)) = device.properties.get("http_port") {
println!(" Connect: http://{}:{}", device.from.ip(), port);
}
}
Ok(())
}
Get a snapshot of all devices discovered so far:
use sood::{service_ids, Sood};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut sood = Sood::new()?;
sood.start().await?;
sood.discover_service(service_ids::ROON_API).await?;
// Wait a bit for responses
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// Get all discovered devices
let devices = sood.get_discovered_devices().await;
println!("Found {} devices:", devices.len());
for device in devices {
println!(" - {} at {}", device.unique_id, device.from);
}
Ok(())
}
For other SOOD queries, use the query() method:
use sood::Sood;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut sood = Sood::new()?;
sood.start().await?;
// Complex query with multiple properties
let mut props = HashMap::new();
props.insert("foo".to_string(), Some("00000000-0000-0000-0000-000000000000".to_string()));
props.insert("bar".to_string(), Some("1.0".to_string()));
sood.query(props).await?;
Ok(())
}
For advanced use cases, you can access raw query responses directly:
use sood::{service_ids, Sood};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut sood = Sood::new()?;
sood.start().await?;
// Build query properties
let mut props = HashMap::new();
props.insert("query_service_id".to_string(), Some(service_ids::ROON_API.to_string()));
// Send query and get response stream
let mut responses = sood.query(props).await?;
// Process raw response messages (including non-device responses)
while let Some(msg) = responses.recv().await {
println!("Response from {}: {:?}", msg.from, msg.properties);
}
Ok(())
}
This crate includes a complete example that demonstrates device discovery:
cd sood
cargo run --example discover
The example will:
This library works on:
Network interface monitoring and multicast support may vary by platform.
The library uses the tracing crate for logging. Enable logging in your application:
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
Licensed under the MIT license (LICENSE).
Contributions are welcome! Please feel free to submit a Pull Request.
RUST_LOG=sood=debug to see detailed loggingOn some systems, binding to multicast addresses may require elevated privileges:
sudo cargo run --example discover
The library checks for interface changes every 5 seconds. If you need faster detection, this can be adjusted in the source code.