Developer SDK

Rust examples

Install the SDK · View source on GitHub ↗

messages/src/main.rs

mod codec;
#[path = "../../integration/session.rs"]
mod session;
use codec::{Frame, decode, encode};
use std::{
    ffi::{CStr, CString, c_char, c_void},
    io,
    sync::mpsc::{self, SyncSender},
    time::{Duration, Instant},
};
use urnetwork_sdk::{native, take_string};
enum Event {
    Message(String, Vec<u8>),
    Peers(Option<String>),
    Query(bool, Option<String>),
}
unsafe fn copied(p: *const c_char) -> Option<String> {
    if p.is_null() {
        None
    } else {
        Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
    }
}
unsafe extern "C" fn message(
    context: *mut c_void,
    protocol: i64,
    source: *const c_char,
    bytes: *const u8,
    length: i32,
) {
    if protocol != 4096 || !(16..=4112).contains(&length) || source.is_null() || bytes.is_null() {
        return;
    }
    let sender = unsafe { &*context.cast::<SyncSender<Event>>() };
    let source = unsafe { CStr::from_ptr(source) }
        .to_string_lossy()
        .into_owned();
    let data = unsafe { std::slice::from_raw_parts(bytes, length as usize) }.to_vec(); // Copy before callback return.
    let _ = sender.try_send(Event::Message(source, data));
}
unsafe extern "C" fn peers(context: *mut c_void, json: *const c_char) {
    let sender = unsafe { &*context.cast::<SyncSender<Event>>() };
    let _ = sender.try_send(Event::Peers(unsafe { copied(json) }));
}
unsafe extern "C" fn query(context: *mut c_void, json: *const c_char, ok: bool) {
    let sender = unsafe { &*context.cast::<SyncSender<Event>>() };
    let _ = sender.try_send(Event::Query(ok, unsafe { copied(json) }));
}
fn show_peers(json: Option<String>) -> Result<bool, Box<dyn std::error::Error>> {
    let value: serde_json::Value = serde_json::from_str(json.as_deref().unwrap_or("null"))?;
    if value.is_null() {
        println!("peers unavailable (no snapshot)");
        return Ok(false);
    }
    println!("{}", value);
    if let Some(peers) = value["Connected"].as_array() {
        for peer in peers {
            if let Some(id) = peer["ClientId"].as_str() {
                let id_c = CString::new(id)?;
                let color = unsafe { take_string((native()?.urnet_get_color_hex)(id_c.as_ptr())) };
                println!("color {id} {}", color.as_deref().unwrap_or("unavailable"));
            }
        }
    }
    Ok(true)
}
fn send(handle: u64, destination: &str, frame: Frame) -> Result<(), Box<dyn std::error::Error>> {
    let id = CString::new(destination)?;
    let bytes = encode(&frame)?;
    if !unsafe {
        (native()?.urnet_device_local_send_subprotocol_bytes)(
            handle,
            4096,
            id.as_ptr(),
            bytes.as_ptr(),
            bytes.len() as i32,
        )
    } {
        return Err(io::Error::other("SDK did not enqueue message").into());
    }
    Ok(())
}
struct Sub(u64);
impl Drop for Sub {
    fn drop(&mut self) {
        if let Ok(raw) = native() {
            unsafe {
                (raw.urnet_sub_close)(self.0);
                (raw.urnet_release)(self.0);
            }
        }
    }
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<_> = std::env::args().skip(1).collect();
    if args == ["--self-test"] {
        codec::self_test()?;
        println!("URMS codec self-test passed");
        return Ok(());
    }
    if args == ["--version"] {
        println!("{}", urnetwork_sdk::version()?);
        return Ok(());
    }
    if args.is_empty()
        || !["self", "peers", "watch", "send"].contains(&args[0].as_str())
        || (args[0] == "send" && args.len() < 3)
    {
        return Err(
            "usage: --self-test | --version | self | peers | watch | send CLIENT_ID TEXT".into(),
        );
    }
    let destination = if args[0] == "send" {
        Some(args[1].to_lowercase())
    } else {
        None
    };
    let raw = native()?;
    if let Some(id) = &destination {
        let id = CString::new(id.as_str())?;
        let mut error = std::ptr::null_mut();
        let parsed = unsafe { take_string((raw.urnet_parse_id)(id.as_ptr(), &mut error)) };
        if let Some(error) = unsafe { take_string(error) } {
            return Err(error.into());
        }
        if parsed.is_none() {
            return Err("invalid client ID".into());
        }
    }
    let (tx, rx) = mpsc::sync_channel(256);
    // One tiny process-lifetime callback context. A late native callback can never
    // dereference freed memory, including during Device shutdown or a query timeout.
    let callback_context = Box::leak(Box::new(tx)) as *mut SyncSender<Event> as *mut c_void;
    let session = session::Session::open_with_connect(false)?;
    let h = session.device.handle()?;
    let mut error = std::ptr::null_mut();
    let sub = unsafe {
        (raw.urnet_device_local_enable_subprotocol)(
            h,
            4096,
            Some(message),
            callback_context,
            &mut error,
        )
    };
    if let Some(error) = unsafe { take_string(error) } {
        return Err(error.into());
    }
    if sub == 0 {
        return Err("subprotocol registration failed".into());
    }
    let _sub = Sub(sub);
    let _peers_sub = Sub(unsafe {
        (raw.urnet_device_add_network_peers_change_listener)(h, Some(peers), callback_context)
    });
    unsafe { (raw.urnet_device_set_provide_mode)(h, 1) };
    println!(
        "self: {}",
        unsafe { take_string((raw.urnet_device_get_client_id)(h)) }.unwrap_or_default()
    );
    if args[0] == "self" {
        return Ok(());
    }
    if show_peers(unsafe { take_string((raw.urnet_device_get_network_peers)(h)) })?
        && args[0] == "peers"
    {
        return Ok(());
    }
    let mut queried = false;
    let mut deadline = Instant::now() + Duration::from_secs(30);
    let mut random = [0u8; 8];
    getrandom::fill(&mut random).map_err(|error| io::Error::other(error.to_string()))?;
    let pending = u64::from_be_bytes(random).max(1);
    loop {
        if let Some(id) = &destination {
            if !queried && unsafe { (raw.urnet_device_local_get_provider_connected)(h) } {
                queried = true;
                let id = CString::new(id.as_str())?;
                unsafe {
                    (raw.urnet_device_local_query_subprotocols)(
                        h,
                        id.as_ptr(),
                        10000,
                        Some(query),
                        callback_context,
                    )
                }
            }
        }
        if let Ok(event) = rx.recv_timeout(Duration::from_millis(100)) {
            match event {
                Event::Peers(json) => {
                    if show_peers(json)? && args[0] == "peers" {
                        return Ok(());
                    }
                }
                Event::Query(ok, json) => {
                    let ids: Option<Vec<i64>> =
                        serde_json::from_str(json.as_deref().unwrap_or("null"))?;
                    if !ok || !ids.unwrap_or_default().contains(&4096) {
                        return Err("peer query failed or peer does not advertise 4096".into());
                    }
                    send(
                        h,
                        destination.as_deref().ok_or("unsolicited query result")?,
                        Frame {
                            kind: 1,
                            id: pending,
                            text: args[2..].join(" "),
                        },
                    )?;
                    println!("sent: {pending} waiting for ACK");
                    deadline = Instant::now() + Duration::from_secs(10);
                }
                Event::Message(source, bytes) => {
                    let frame = match decode(&bytes) {
                        Ok(frame) => frame,
                        Err(error) => {
                            eprintln!("rejected frame: {error}");
                            continue;
                        }
                    };
                    println!(
                        "kind={} source={} id={} text={:?}",
                        frame.kind, source, frame.id, frame.text
                    );
                    if frame.kind == 1 {
                        send(
                            h,
                            &source,
                            Frame {
                                kind: 2,
                                id: frame.id,
                                text: String::new(),
                            },
                        )?
                    } else if destination.as_deref() == Some(source.as_str()) && frame.id == pending
                    {
                        return Ok(());
                    }
                }
            }
        }
        if args[0] != "watch" && Instant::now() > deadline {
            return Err("connection, peer snapshot, query, or ACK timed out".into());
        }
    }
}
fn main() {
    if let Err(error) = run() {
        eprintln!("{error}");
        std::process::exit(1)
    }
}