extern crate alloc;
use alloc::vec::Vec;
use crate::io;
use crate::sys;
use super::get_arg;
pub fn arp(argc: i32, argv: *const *const u8) -> i32 {
let mut numeric = false;
let mut verbose = false;
let mut filter_iface: Option<&[u8]> = None;
let mut i = 1;
while i < argc as usize {
let arg = match unsafe { get_arg(argv, i as i32) } {
Some(a) => a,
None => break,
};
if arg == b"-n" {
numeric = true;
} else if arg == b"-v" {
verbose = true;
} else if arg == b"-a" {
} else if arg == b"-i" {
i += 1;
filter_iface = unsafe { get_arg(argv, i as i32) };
} else if arg == b"-h" || arg == b"--help" {
io::write_str(1, b"Usage: arp [-a] [-n] [-v] [-i interface]\n");
io::write_str(1, b" -a BSD style output\n");
io::write_str(1, b" -n Don't resolve names\n");
io::write_str(1, b" -v Verbose\n");
io::write_str(1, b" -i Show only specified interface\n");
return 0;
}
i += 1;
}
let _ = numeric; let _ = verbose;
io::write_str(1, b"Address HWtype HWaddress Flags Mask Iface\n");
let fd = io::open(b"/proc/net/arp", libc::O_RDONLY, 0);
if fd < 0 {
io::write_str(2, b"arp: cannot open /proc/net/arp\n");
return 1;
}
let content = io::read_all(fd);
io::close(fd);
for (idx, line) in content.split(|&c| c == b'\n').enumerate() {
if idx == 0 || line.is_empty() {
continue; }
let fields: Vec<&[u8]> = line.split(|&c| c == b' ')
.filter(|s| !s.is_empty())
.collect();
if fields.len() < 6 {
continue;
}
let ip_addr = fields[0];
let hw_type = fields[1];
let flags = fields[2];
let hw_addr = fields[3];
let mask = fields[4];
let iface = fields[5];
if let Some(filter) = filter_iface {
if iface != filter {
continue;
}
}
io::write_all(1, ip_addr);
pad_to(1, ip_addr.len(), 25);
let hw_type_str = match hw_type {
b"0x1" => b"ether" as &[u8],
b"0x6" => b"ieee802",
b"0x17" => b"ax25",
b"0x19" => b"arcnet",
_ => hw_type,
};
io::write_all(1, hw_type_str);
pad_to(1, hw_type_str.len(), 8);
io::write_all(1, hw_addr);
pad_to(1, hw_addr.len(), 20);
let flags_char = decode_arp_flags(flags);
io::write_all(1, &[flags_char]);
io::write_str(1, b" ");
io::write_all(1, mask);
pad_to(1, mask.len(), 16);
io::write_all(1, iface);
io::write_str(1, b"\n");
}
0
}
fn decode_arp_flags(flags: &[u8]) -> u8 {
let val = if flags.starts_with(b"0x") {
parse_hex(&flags[2..])
} else {
sys::parse_u64(flags).unwrap_or(0) as u32
};
if val & 0x04 != 0 {
b'M' } else if val & 0x02 != 0 {
b'C' } else {
b' '
}
}
fn parse_hex(s: &[u8]) -> u32 {
let mut result: u32 = 0;
for &c in s {
let digit = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => break,
};
result = result.wrapping_mul(16).wrapping_add(digit as u32);
}
result
}
fn pad_to(fd: i32, current: usize, target: usize) {
for _ in current..target {
io::write_str(fd, b" ");
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::process::Command;
use std::path::PathBuf;
fn get_armybox_path() -> PathBuf {
if let Ok(path) = std::env::var("ARMYBOX_PATH") {
return PathBuf::from(path);
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap());
let release = manifest_dir.join("target/release/armybox");
if release.exists() { return release; }
manifest_dir.join("target/debug/armybox")
}
#[test]
fn test_arp_runs() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["arp"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(0));
let stdout = std::string::String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Address"));
assert!(stdout.contains("HWtype"));
}
#[test]
fn test_arp_help() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["arp", "-h"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(0));
let stdout = std::string::String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage"));
}
}