ipc-ring48 1.0.0

A bounded 48-byte POSIX shared-memory SPSC queue for Rust.
Documentation
use ipc_ring48::{Consumer, PAYLOAD_SIZE, Producer, PushError, SHM_NAME, stats, unlink};
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::process;

fn read_up_to_48_from_reader<R: Read>(mut reader: R) -> io::Result<[u8; PAYLOAD_SIZE]> {
    let mut out = [0u8; PAYLOAD_SIZE];
    let mut filled = 0;

    while filled < PAYLOAD_SIZE {
        let n = reader.read(&mut out[filled..])?;

        if n == 0 {
            break;
        }

        filled += n;
    }

    Ok(out)
}

fn bytes_from_text_argument(arg: &str) -> [u8; PAYLOAD_SIZE] {
    let input = arg.as_bytes();

    if input.len() > PAYLOAD_SIZE {
        eprintln!(
            "input is {} bytes; truncating to {} bytes",
            input.len(),
            PAYLOAD_SIZE
        );
    }

    let mut out = [0u8; PAYLOAD_SIZE];
    let n = input.len().min(PAYLOAD_SIZE);

    out[..n].copy_from_slice(&input[..n]);

    out
}

fn bytes_from_hex_argument(arg: &str) -> Result<[u8; PAYLOAD_SIZE], String> {
    let hex = arg.trim().replace([' ', '_', ':', '-'], "");

    if hex.len() != PAYLOAD_SIZE * 2 {
        return Err(format!(
            "expected exactly {} hex characters for {} bytes",
            PAYLOAD_SIZE * 2,
            PAYLOAD_SIZE
        ));
    }

    let mut out = [0u8; PAYLOAD_SIZE];

    for (i, byte) in out.iter_mut().enumerate() {
        let start = i * 2;
        let end = start + 2;

        *byte = u8::from_str_radix(&hex[start..end], 16)
            .map_err(|_| format!("invalid hex byte at index {}", i))?;
    }

    Ok(out)
}

fn bytes_from_u64_arguments(values: Vec<String>) -> Result<[u8; PAYLOAD_SIZE], String> {
    if values.len() != 6 {
        return Err(format!(
            "expected exactly 6 u64 values, got {}",
            values.len()
        ));
    }

    let mut out = [0u8; PAYLOAD_SIZE];

    for (i, value) in values.iter().enumerate() {
        let number = value
            .parse::<u64>()
            .map_err(|_| format!("invalid u64 at argument {}: {}", i + 1, value))?;

        let bytes = number.to_le_bytes();
        let start = i * 8;

        out[start..start + 8].copy_from_slice(&bytes);
    }

    Ok(out)
}

fn print_payload_text(payload: &[u8; PAYLOAD_SIZE]) {
    let text = payload
        .iter()
        .copied()
        .take_while(|b| *b != 0)
        .map(|b| {
            if b.is_ascii_graphic() || b == b' ' {
                b as char
            } else {
                '.'
            }
        })
        .collect::<String>();

    println!("{}", text);
}

fn print_payload_hex(payload: &[u8; PAYLOAD_SIZE]) {
    for byte in payload {
        print!("{:02x}", byte);
    }

    println!();
}

fn write_payload_bin(payload: &[u8; PAYLOAD_SIZE]) -> io::Result<()> {
    let mut stdout = io::stdout().lock();
    stdout.write_all(payload)?;
    stdout.flush()
}

fn print_payload_u64(payload: &[u8; PAYLOAD_SIZE]) {
    for i in 0..6 {
        let start = i * 8;

        let mut bytes = [0u8; 8];
        bytes.copy_from_slice(&payload[start..start + 8]);

        let value = u64::from_le_bytes(bytes);

        if i > 0 {
            print!(" ");
        }

        print!("{}", value);
    }

    println!();
}

fn print_payload_full(payload: &[u8; PAYLOAD_SIZE]) {
    print!("text: ");
    print_payload_text(payload);

    print!("hex : ");
    print_payload_hex(payload);

    print!("u64 : ");
    print_payload_u64(payload);
}

fn usage() {
    eprintln!("Usage:");
    eprintln!("  ipc-ring48 init <power-of-two capacity>");
    eprintln!("  ipc-ring48 push-text <value>");
    eprintln!("  ipc-ring48 push-file <path>");
    eprintln!("  ipc-ring48 push-stdin");
    eprintln!("  ipc-ring48 push-hex <96 hex chars>");
    eprintln!("  ipc-ring48 push-u64 <a> <b> <c> <d> <e> <f>");
    eprintln!("  ipc-ring48 pop");
    eprintln!("  ipc-ring48 pop-text");
    eprintln!("  ipc-ring48 pop-hex");
    eprintln!("  ipc-ring48 pop-bin");
    eprintln!("  ipc-ring48 pop-u64");
    eprintln!("  ipc-ring48 stats");
    eprintln!("  ipc-ring48 unlink");
}

fn push_or_exit(bytes: [u8; PAYLOAD_SIZE]) -> io::Result<()> {
    let producer = Producer::open()?;

    match producer.push(bytes) {
        Ok(()) => Ok(()),
        Err(PushError::Full) => {
            eprintln!("queue full");
            process::exit(2);
        }
    }
}

fn pop_or_exit() -> io::Result<[u8; PAYLOAD_SIZE]> {
    let consumer = Consumer::open()?;

    match consumer.pop() {
        Some(bytes) => Ok(bytes),
        None => {
            eprintln!("queue empty");
            process::exit(2);
        }
    }
}

fn main() -> io::Result<()> {
    let mut args = env::args().skip(1);

    let Some(command) = args.next() else {
        usage();
        return Ok(());
    };

    match command.as_str() {
        "init" => {
            let Some(capacity) = args.next() else {
                eprintln!("missing capacity");
                usage();
                return Ok(());
            };

            let capacity = capacity
                .parse::<usize>()
                .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid capacity"))?;

            let producer = Producer::create_or_open(capacity)?;
            let stats = producer.stats();

            println!("initialised {}", SHM_NAME);
            println!("capacity: {}", stats.capacity);
            println!("len     : {}", stats.len);
            println!("head    : {}", stats.head);
            println!("tail    : {}", stats.tail);
        }

        "push" | "push-text" => {
            let Some(value) = args.next() else {
                eprintln!("missing value");
                usage();
                return Ok(());
            };

            push_or_exit(bytes_from_text_argument(&value))?;
        }

        "push-file" => {
            let Some(path) = args.next() else {
                eprintln!("missing file path");
                usage();
                return Ok(());
            };

            let file = File::open(path)?;
            let bytes = read_up_to_48_from_reader(file)?;

            push_or_exit(bytes)?;
        }

        "push-stdin" => {
            let stdin = io::stdin().lock();
            let bytes = read_up_to_48_from_reader(stdin)?;

            push_or_exit(bytes)?;
        }

        "push-hex" => {
            let Some(value) = args.next() else {
                eprintln!("missing hex value");
                usage();
                return Ok(());
            };

            let bytes = match bytes_from_hex_argument(&value) {
                Ok(bytes) => bytes,
                Err(err) => {
                    eprintln!("{}", err);
                    process::exit(2);
                }
            };

            push_or_exit(bytes)?;
        }

        "push-u64" => {
            let values = args.collect::<Vec<_>>();

            let bytes = match bytes_from_u64_arguments(values) {
                Ok(bytes) => bytes,
                Err(err) => {
                    eprintln!("{}", err);
                    process::exit(2);
                }
            };

            push_or_exit(bytes)?;
        }

        "pop" => {
            let payload = pop_or_exit()?;

            print_payload_full(&payload);
        }

        "pop-text" => {
            let payload = pop_or_exit()?;

            print_payload_text(&payload);
        }

        "pop-hex" => {
            let payload = pop_or_exit()?;

            print_payload_hex(&payload);
        }

        "pop-bin" => {
            let payload = pop_or_exit()?;

            write_payload_bin(&payload)?;
        }

        "pop-u64" => {
            let payload = pop_or_exit()?;

            print_payload_u64(&payload);
        }

        "stats" => {
            let stats = stats()?;

            println!("capacity   : {}", stats.capacity);
            println!("len        : {}", stats.len);
            println!("head       : {}", stats.head);
            println!("tail       : {}", stats.tail);
            println!("region_size: {}", stats.region_size);
        }

        "unlink" => {
            unlink()?;
            println!("unlinked {}", SHM_NAME);
        }

        _ => {
            eprintln!("unknown command: {}", command);
            usage();
        }
    }

    Ok(())
}