NORM-mirror/src/rust/API_GUIDE.md

6.3 KiB

NORM Rust Bindings API Guide

This guide provides a detailed overview of the main components of the NORM Rust bindings API.

Core Components

The NORM Rust API follows a hierarchical structure similar to the underlying C API but with Rust idioms and safety guarantees:

Instance
  └── Session
       ├── Object (Data)
       ├── Object (File)
       └── Object (Stream)

Instance

Instance is the top-level object that represents a NORM protocol instance. It's the starting point for all NORM operations.

use norm::Instance;

// Create a new NORM instance
let instance = Instance::new(false)?; // false = no priority boost

// Set cache directory for receiving files
instance.set_cache_directory("/tmp/norm")?;

// Process events
for event in instance.events() {
    // Handle events
}

Session

A Session represents a NORM protocol session, which can operate in sender mode, receiver mode, or both.

use norm::{Instance, Session};

let instance = Instance::new(false)?;

// Create a session with address, port, and node ID
let session = instance.create_session("224.1.2.3", 6003, 1)?;

// Configure session
session
    .set_tx_rate(1_000_000.0) // 1 Mbps
    .set_ttl(64)?
    .set_loopback(true)?;

// Start sender
session.start_sender(
    rand::random(), // Session ID
    1024 * 1024,    // Buffer space (1 MB)
    1400,           // Segment size
    64,             // Data segments per block
    16,             // Parity segments per block
    None,           // FEC ID (default = 0)
)?;

// Or start receiver
session.start_receiver(1024 * 1024)?;

Objects

NORM supports three types of objects for data transfer:

  1. Data Objects: For memory buffer transfers
  2. File Objects: For file transfers
  3. Stream Objects: For continuous data streaming
// Send data
let data = b"Hello, NORM!";
let data_obj = session.data_enqueue(data, Some(b"Info"))?;

// Send file
let file_obj = session.file_enqueue("/path/to/file.txt", Some(b"file.txt"))?;

// Open stream
let stream_obj = session.stream_open(64 * 1024, Some(b"Stream info"))?;

Events

NORM uses an event-based model for signaling state changes. The Rust bindings provide an iterator-based approach for event handling.

// Process events using iterator
for event in instance.events() {
    match event.event_type {
        EventType::RxObjectCompleted => {
            let object = Object::from_handle_unowned(event.object);

            // Handle based on object type
            match object.get_type() {
                ObjectType::Data => {
                    if let Ok(data) = object.access_data() {
                        println!("Received data: {:?}", data);
                    }
                },
                ObjectType::File => {
                    if let Ok(info) = object.get_info() {
                        println!("Received file: {}", String::from_utf8_lossy(&info));
                    }
                },
                _ => {}
            }
        },
        EventType::TxObjectSent => println!("Object sent"),
        _ => {} // Handle other events
    }
}

Multicast Configuration

The Rust bindings provide an ergonomic API for configuring multicast:

use norm::{multicast, MulticastExt};

// Configure multicast with builder pattern
let config = multicast!("224.1.2.3", 6003, {
    ttl: 64,
    interface: "eth0",
    loopback: true,
    ssm_source: "192.168.1.1",
});

// Apply configuration to session
session.with_multicast(&config)?;

Error Handling

All operations that might fail return a Result<T, Error> type:

match instance.set_cache_directory("/nonexistent/path") {
    Ok(()) => println!("Cache directory set"),
    Err(e) => match e {
        Error::FileError(msg) => println!("File error: {}", msg),
        Error::InvalidParameter => println!("Invalid parameter"),
        _ => println!("Other error: {:?}", e),
    }
}

Ownership and Lifetimes

The Rust bindings use RAII (Resource Acquisition Is Initialization) to ensure proper resource management:

  • Instance, Session, and Object implement Drop to automatically clean up resources
  • Objects created directly have ownership and will be freed when dropped
  • Objects obtained from events are not owned and are marked as such
// Owned object from direct creation
let data_obj = session.data_enqueue(data, None)?;
// Will be released when data_obj goes out of scope

// Non-owned object from event
let object = Object::from_handle_unowned(event.object);
// Will NOT be released when object goes out of scope

Utility Functions

The API provides several utility functions:

// Check if an address is a multicast address
let is_mcast = is_multicast_address("224.1.2.3");

// Get NORM version
let (major, minor, patch) = norm::version();

Advanced Features

Custom Memory Allocation

unsafe {
    instance.set_allocation_functions(
        my_alloc_function,
        my_free_function
    );
}

File Transfers

When receiving files, you must set a cache directory:

instance.set_cache_directory("/tmp/norm_files")?;

Stream Management

For stream objects, additional methods are available on the Object type:

// Open a stream
let stream = session.stream_open(64 * 1024, Some(b"Stream info"))?;

// Write to stream
let bytes_written = stream.stream_write(b"Hello, stream!")?;

// Check if stream has space for more data
if stream.stream_has_vacancy()? {
    stream.stream_write(b"More data")?;
}

// Mark end of message
stream.stream_mark_eom()?;

// Flush stream with end-of-message marker
stream.stream_flush(true, FlushMode::Passive)?;

// Read from stream (receiver side)
let mut buffer = vec![0u8; 1024];
let bytes_read = stream.stream_read(&mut buffer)?;

// Seek to next message start
if stream.stream_seek_msg_start()? {
    println!("Found next message");
}

// Close stream gracefully
stream.stream_close(true)?;

Best Practices

  1. Always check return values - Use the ? operator or explicitly handle errors
  2. Process all events - Use the event iterator to process all events
  3. Close resources properly - Let RAII handle cleanup or explicitly call stop_sender()/stop_receiver()
  4. Configure multicast correctly - Use the ergonomic multicast API
  5. Use appropriate buffer sizes - Match buffer sizes to your application's needs