Skip to main content

udp_tool/
main.rs

1//! udp-tool – UDP scheme probe utility for strat9-os
2//!
3//! This binary exercises the `/net/udp/*` scheme API exposed by `strate-net`.
4//! It binds a local UDP endpoint, sends periodic probes, prints incoming
5//! datagrams, and echoes received payloads back to their source.
6//!
7//! Usage: currently no argv wiring; defaults are used.
8
9#![no_std]
10#![no_main]
11#![feature(alloc_error_handler)]
12
13extern crate alloc;
14
15use core::{alloc::Layout, fmt::Write, panic::PanicInfo};
16use strat9_abi::ip::parse_ipv4_literal;
17use strat9_syscall::{call, data::TimeSpec, number};
18
19alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 96 * 1024;);
20
21#[global_allocator]
22static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
23
24#[alloc_error_handler]
25fn alloc_error(_layout: Layout) -> ! {
26    log("[udp-tool] OOM\n");
27    call::exit(12)
28}
29
30#[panic_handler]
31fn panic(info: &PanicInfo) -> ! {
32    call::handle_panic("udp-tool", info)
33}
34
35struct BufWriter<'a> {
36    buf: &'a mut [u8],
37    pos: usize,
38}
39
40impl core::fmt::Write for BufWriter<'_> {
41    fn write_str(&mut self, s: &str) -> core::fmt::Result {
42        let bytes = s.as_bytes();
43        let avail = self.buf.len().saturating_sub(self.pos);
44        let n = bytes.len().min(avail);
45        self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
46        self.pos += n;
47        Ok(())
48    }
49}
50
51fn log(msg: &str) {
52    let _ = call::write(1, msg.as_bytes());
53}
54
55fn debug(msg: &str) {
56    let _ = call::debug_log(msg.as_bytes());
57}
58
59fn sleep_ms(ms: u64) {
60    let req = TimeSpec {
61        tv_sec: (ms / 1000) as i64,
62        tv_nsec: ((ms % 1000) * 1_000_000) as i64,
63    };
64    let _ = unsafe {
65        strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
66    };
67}
68
69fn clock_ns() -> u64 {
70    unsafe { strat9_syscall::syscall0(number::SYS_CLOCK_GETTIME) }
71        .map(|v| v as u64)
72        .unwrap_or(0)
73}
74
75fn open_rw(path: &str) -> Result<usize, i32> {
76    call::openat(0, path, 0x2, 0)
77        .map(|fd| fd)
78        .map_err(|e| e.to_errno() as i32)
79}
80
81fn read_text(path: &str, out: &mut [u8]) -> usize {
82    let Ok(fd) = call::openat(0, path, 0x0, 0) else {
83        return 0;
84    };
85    let n = call::read(fd, out).unwrap_or(0);
86    let _ = call::close(fd);
87    n
88}
89
90fn parse_first_ipv4_line(path: &str, buf: &mut [u8; 128]) -> Option<[u8; 4]> {
91    let n = read_text(path, buf);
92    if n == 0 {
93        return None;
94    }
95    let line_end = buf[..n].iter().position(|&b| b == b'\n').unwrap_or(n);
96    let mut s = core::str::from_utf8(&buf[..line_end]).ok()?.trim();
97    if let Some((head, _tail)) = s.split_once('/') {
98        s = head;
99    }
100    parse_ipv4_literal(s)
101}
102
103fn ip_to_path<'a>(dst: &[u8; 4], port: u16, out: &'a mut [u8; 96]) -> &'a str {
104    let n = {
105        let mut w = BufWriter { buf: out, pos: 0 };
106        let _ = write!(
107            w,
108            "/net/udp/send/{}.{}.{}.{}/{}",
109            dst[0], dst[1], dst[2], dst[3], port
110        );
111        w.pos
112    };
113    core::str::from_utf8(&out[..n]).unwrap_or("/net/udp/send/0.0.0.0/0")
114}
115
116fn format_src<'a>(src: &[u8; 4], port: u16, out: &'a mut [u8; 64]) -> &'a str {
117    let n = {
118        let mut w = BufWriter { buf: out, pos: 0 };
119        let _ = write!(w, "{}.{}.{}.{}:{}", src[0], src[1], src[2], src[3], port);
120        w.pos
121    };
122    core::str::from_utf8(&out[..n]).unwrap_or("0.0.0.0:0")
123}
124
125fn dump_payload_ascii<'a>(data: &[u8], out: &'a mut [u8; 96]) -> &'a str {
126    let n = data.len().min(out.len());
127    for (i, &b) in data.iter().take(n).enumerate() {
128        out[i] = if (0x20..=0x7e).contains(&b) { b } else { b'.' };
129    }
130    core::str::from_utf8(&out[..n]).unwrap_or("")
131}
132
133#[unsafe(no_mangle)]
134pub extern "C" fn _start() -> ! {
135    const PORT: u16 = 9999;
136    const HEARTBEAT_MS: u64 = 2000;
137
138    log("[udp-tool] starting\n");
139
140    let mut path_buf = [0u8; 96];
141    let bind_path = {
142        let n = {
143            let mut w = BufWriter {
144                buf: &mut path_buf,
145                pos: 0,
146            };
147            let _ = write!(w, "/net/udp/bind/{}", PORT);
148            w.pos
149        };
150        core::str::from_utf8(&path_buf[..n]).unwrap_or("/net/udp/bind/9999")
151    };
152
153    let bind_fd = loop {
154        match open_rw(bind_path) {
155            Ok(fd) => break fd,
156            Err(_) => {
157                debug("[udp-tool] waiting for /net/udp bind\n");
158                sleep_ms(200);
159            }
160        }
161    };
162
163    let mut ip_buf = [0u8; 128];
164    let local_ip = parse_first_ipv4_line("/net/address", &mut ip_buf);
165    let gateway_ip = parse_first_ipv4_line("/net/gateway", &mut ip_buf);
166    let default_target = local_ip.or(gateway_ip).unwrap_or([127, 0, 0, 1]);
167
168    let send_path = ip_to_path(&default_target, PORT, &mut path_buf);
169    let send_fd = open_rw(send_path).ok();
170
171    log("[udp-tool] bound on /net/udp/bind/9999\n");
172    log("[udp-tool] target path: ");
173    log(send_path);
174    log("\n");
175
176    if let Some(fd) = send_fd {
177        let _ = call::write(fd, b"udp-tool: hello\n");
178    }
179
180    let mut last_heartbeat = clock_ns();
181    let mut rx_buf = [0u8; 512];
182    let mut src_txt = [0u8; 64];
183    let mut ascii = [0u8; 96];
184
185    loop {
186        match call::read(bind_fd, &mut rx_buf) {
187            Ok(n) if n >= 6 => {
188                let src = [rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]];
189                let src_port = u16::from_be_bytes([rx_buf[4], rx_buf[5]]);
190                let payload = &rx_buf[6..n];
191
192                log("[udp-tool] rx from ");
193                log(format_src(&src, src_port, &mut src_txt));
194                log(" | ");
195                log(dump_payload_ascii(payload, &mut ascii));
196                log("\n");
197
198                // Echo payload to sender via scheme path.
199                let reply_path = ip_to_path(&src, src_port, &mut path_buf);
200                if let Ok(fd) = open_rw(reply_path) {
201                    let _ = call::write(fd, payload);
202                    let _ = call::close(fd);
203                }
204            }
205            Ok(_) => {
206                // Ignore short frame.
207            }
208            Err(e) => {
209                if e.to_errno() != 11 {
210                    log("[udp-tool] read error\n");
211                }
212            }
213        }
214
215        let now = clock_ns();
216        if now.saturating_sub(last_heartbeat) >= HEARTBEAT_MS * 1_000_000 {
217            if let Some(fd) = send_fd {
218                let _ = call::write(fd, b"udp-tool: heartbeat\n");
219            }
220            last_heartbeat = now;
221        }
222
223        sleep_ms(50);
224    }
225}