1#![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_syscall::{call, data::TimeSpec, number};
17
18alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 96 * 1024;);
19
20#[global_allocator]
21static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
22
23#[alloc_error_handler]
24fn alloc_error(_layout: Layout) -> ! {
25 log("[udp-tool] OOM\n");
26 call::exit(12)
27}
28
29#[panic_handler]
30fn panic(info: &PanicInfo) -> ! {
31 log("[udp-tool] PANIC: ");
32 let mut buf = [0u8; 256];
33 let n = {
34 let mut w = BufWriter {
35 buf: &mut buf,
36 pos: 0,
37 };
38 let _ = write!(w, "{}", info.message());
39 w.pos
40 };
41 if let Ok(s) = core::str::from_utf8(&buf[..n]) {
42 log(s);
43 }
44 log("\n");
45 call::exit(255)
46}
47
48struct BufWriter<'a> {
49 buf: &'a mut [u8],
50 pos: usize,
51}
52
53impl core::fmt::Write for BufWriter<'_> {
54 fn write_str(&mut self, s: &str) -> core::fmt::Result {
55 let bytes = s.as_bytes();
56 let avail = self.buf.len().saturating_sub(self.pos);
57 let n = bytes.len().min(avail);
58 self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
59 self.pos += n;
60 Ok(())
61 }
62}
63
64fn log(msg: &str) {
65 let _ = call::write(1, msg.as_bytes());
66}
67
68fn debug(msg: &str) {
69 let _ = call::debug_log(msg.as_bytes());
70}
71
72fn sleep_ms(ms: u64) {
73 let req = TimeSpec {
74 tv_sec: (ms / 1000) as i64,
75 tv_nsec: ((ms % 1000) * 1_000_000) as i64,
76 };
77 let _ = unsafe {
78 strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
79 };
80}
81
82fn clock_ns() -> u64 {
83 unsafe { strat9_syscall::syscall0(number::SYS_CLOCK_GETTIME) }
84 .map(|v| v as u64)
85 .unwrap_or(0)
86}
87
88fn open_rw(path: &str) -> Result<usize, i32> {
89 call::openat(0, path, 0x2, 0)
90 .map(|fd| fd as usize)
91 .map_err(|e| e.to_errno() as i32)
92}
93
94fn read_text(path: &str, out: &mut [u8]) -> usize {
95 let Ok(fd) = call::openat(0, path, 0x0, 0) else {
96 return 0;
97 };
98 let n = call::read(fd as usize, out).unwrap_or(0);
99 let _ = call::close(fd as usize);
100 n
101}
102
103fn parse_ipv4_literal(s: &str) -> Option<[u8; 4]> {
104 let mut octets = [0u8; 4];
105 let mut idx = 0usize;
106 let mut val: u16 = 0;
107 let mut has_digit = false;
108
109 for &b in s.as_bytes() {
110 if b == b'.' {
111 if !has_digit || idx >= 3 || val > 255 {
112 return None;
113 }
114 octets[idx] = val as u8;
115 idx += 1;
116 val = 0;
117 has_digit = false;
118 continue;
119 }
120 if !b.is_ascii_digit() {
121 return None;
122 }
123 val = val * 10 + (b - b'0') as u16;
124 has_digit = true;
125 }
126
127 if !has_digit || idx != 3 || val > 255 {
128 return None;
129 }
130
131 octets[3] = val as u8;
132 Some(octets)
133}
134
135fn parse_first_ipv4_line(path: &str, buf: &mut [u8; 128]) -> Option<[u8; 4]> {
136 let n = read_text(path, buf);
137 if n == 0 {
138 return None;
139 }
140 let line_end = buf[..n].iter().position(|&b| b == b'\n').unwrap_or(n);
141 let mut s = core::str::from_utf8(&buf[..line_end]).ok()?.trim();
142 if let Some((head, _tail)) = s.split_once('/') {
143 s = head;
144 }
145 parse_ipv4_literal(s)
146}
147
148fn ip_to_path<'a>(dst: &[u8; 4], port: u16, out: &'a mut [u8; 96]) -> &'a str {
149 let n = {
150 let mut w = BufWriter { buf: out, pos: 0 };
151 let _ = write!(
152 w,
153 "/net/udp/send/{}.{}.{}.{}/{}",
154 dst[0], dst[1], dst[2], dst[3], port
155 );
156 w.pos
157 };
158 core::str::from_utf8(&out[..n]).unwrap_or("/net/udp/send/0.0.0.0/0")
159}
160
161fn format_src<'a>(src: &[u8; 4], port: u16, out: &'a mut [u8; 64]) -> &'a str {
162 let n = {
163 let mut w = BufWriter { buf: out, pos: 0 };
164 let _ = write!(w, "{}.{}.{}.{}:{}", src[0], src[1], src[2], src[3], port);
165 w.pos
166 };
167 core::str::from_utf8(&out[..n]).unwrap_or("0.0.0.0:0")
168}
169
170fn dump_payload_ascii<'a>(data: &[u8], out: &'a mut [u8; 96]) -> &'a str {
171 let n = data.len().min(out.len());
172 for (i, &b) in data.iter().take(n).enumerate() {
173 out[i] = if (0x20..=0x7e).contains(&b) { b } else { b'.' };
174 }
175 core::str::from_utf8(&out[..n]).unwrap_or("")
176}
177
178#[unsafe(no_mangle)]
179pub extern "C" fn _start() -> ! {
180 const PORT: u16 = 9999;
181 const HEARTBEAT_MS: u64 = 2000;
182
183 log("[udp-tool] starting\n");
184
185 let mut path_buf = [0u8; 96];
186 let bind_path = {
187 let n = {
188 let mut w = BufWriter {
189 buf: &mut path_buf,
190 pos: 0,
191 };
192 let _ = write!(w, "/net/udp/bind/{}", PORT);
193 w.pos
194 };
195 core::str::from_utf8(&path_buf[..n]).unwrap_or("/net/udp/bind/9999")
196 };
197
198 let bind_fd = loop {
199 match open_rw(bind_path) {
200 Ok(fd) => break fd,
201 Err(_) => {
202 debug("[udp-tool] waiting for /net/udp bind\n");
203 sleep_ms(200);
204 }
205 }
206 };
207
208 let mut ip_buf = [0u8; 128];
209 let local_ip = parse_first_ipv4_line("/net/address", &mut ip_buf);
210 let gateway_ip = parse_first_ipv4_line("/net/gateway", &mut ip_buf);
211 let default_target = local_ip.or(gateway_ip).unwrap_or([127, 0, 0, 1]);
212
213 let send_path = ip_to_path(&default_target, PORT, &mut path_buf);
214 let send_fd = open_rw(send_path).ok();
215
216 log("[udp-tool] bound on /net/udp/bind/9999\n");
217 log("[udp-tool] target path: ");
218 log(send_path);
219 log("\n");
220
221 if let Some(fd) = send_fd {
222 let _ = call::write(fd, b"udp-tool: hello\n");
223 }
224
225 let mut last_heartbeat = clock_ns();
226 let mut rx_buf = [0u8; 512];
227 let mut src_txt = [0u8; 64];
228 let mut ascii = [0u8; 96];
229
230 loop {
231 match call::read(bind_fd, &mut rx_buf) {
232 Ok(n) if n >= 6 => {
233 let src = [rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]];
234 let src_port = u16::from_be_bytes([rx_buf[4], rx_buf[5]]);
235 let payload = &rx_buf[6..n];
236
237 log("[udp-tool] rx from ");
238 log(format_src(&src, src_port, &mut src_txt));
239 log(" | ");
240 log(dump_payload_ascii(payload, &mut ascii));
241 log("\n");
242
243 let reply_path = ip_to_path(&src, src_port, &mut path_buf);
245 if let Ok(fd) = open_rw(reply_path) {
246 let _ = call::write(fd, payload);
247 let _ = call::close(fd);
248 }
249 }
250 Ok(_) => {
251 }
253 Err(e) => {
254 if e.to_errno() != 11 {
255 log("[udp-tool] read error\n");
256 }
257 }
258 }
259
260 let now = clock_ns();
261 if now.saturating_sub(last_heartbeat) >= HEARTBEAT_MS * 1_000_000 {
262 if let Some(fd) = send_fd {
263 let _ = call::write(fd, b"udp-tool: heartbeat\n");
264 }
265 last_heartbeat = now;
266 }
267
268 sleep_ms(50);
269 }
270}