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(())
}