Skip to main content

strat9_kernel/shell/commands/util/
ntpdate.rs

1use super::*;
2use alloc::string::String;
3use strat9_abi::ip::parse_ipv4_literal;
4
5const NTP_PORT: u16 = 123;
6const NTP_UNIX_EPOCH_DELTA: u64 = 2_208_988_800; // 1900 -> 1970
7
8/// Resolves ntp server.
9fn resolve_ntp_server(server: &str) -> Result<[u8; 4], ShellError> {
10    if let Some(ip) = parse_ipv4_literal(server) {
11        return Ok(ip);
12    }
13
14    let path = alloc::format!("/net/resolve/{}", server);
15    let fd = vfs::open(&path, vfs::OpenFlags::READ).map_err(|_| ShellError::ExecutionFailed)?;
16    let mut buf = [0u8; 64];
17    let n = vfs::read(fd, &mut buf).unwrap_or(0);
18    let _ = vfs::close(fd);
19    if n == 0 {
20        return Err(ShellError::ExecutionFailed);
21    }
22    let end = buf[..n].iter().position(|&b| b == b'\n').unwrap_or(n);
23    let s = core::str::from_utf8(&buf[..end]).unwrap_or("").trim();
24    parse_ipv4_literal(s).ok_or(ShellError::ExecutionFailed)
25}
26
27/// Formats ipv4.
28fn format_ipv4(ip: &[u8; 4]) -> String {
29    alloc::format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])
30}
31
32/// Sends an NTP request over UDP scheme and returns server transmit timestamp
33/// as unix seconds + nanoseconds.
34fn ntp_query(server_ip: &[u8; 4]) -> Result<(u64, u32), ShellError> {
35    let path = alloc::format!(
36        "/net/udp/connect/{}.{}.{}/{}/{}",
37        server_ip[0],
38        server_ip[1],
39        server_ip[2],
40        server_ip[3],
41        NTP_PORT
42    );
43    let fd = vfs::open(&path, vfs::OpenFlags::RDWR).map_err(|_| ShellError::ExecutionFailed)?;
44
45    // NTP packet: 48 bytes. LI=0, VN=4, Mode=3 (client).
46    let mut req = [0u8; 48];
47    req[0] = 0x23;
48    req[2] = 6; // poll interval hint
49    req[3] = 0xEC; // precision (~2^-20)
50
51    // Use local monotonic clock as entropy for transmit fraction.
52    let now_ns = crate::syscall::time::current_time_ns();
53    let frac = (((now_ns % 1_000_000_000) as u128) << 32) / 1_000_000_000u128;
54    req[44..48].copy_from_slice(&(frac as u32).to_be_bytes());
55
56    if vfs::write(fd, &req).is_err() {
57        let _ = vfs::close(fd);
58        return Err(ShellError::ExecutionFailed);
59    }
60
61    let start_tick = crate::process::scheduler::ticks();
62    let timeout_ticks = crate::arch::x86_64::timer::TIMER_HZ * 3; // ~3s
63    let mut resp = [0u8; 64];
64
65    loop {
66        match vfs::read(fd, &mut resp) {
67            Ok(n) if n >= 48 => {
68                let _ = vfs::close(fd);
69                let li_vn_mode = resp[0];
70                let mode = li_vn_mode & 0x07;
71                let stratum = resp[1];
72                if mode != 4 || stratum == 0 {
73                    return Err(ShellError::ExecutionFailed);
74                }
75
76                let ntp_secs = u32::from_be_bytes([resp[40], resp[41], resp[42], resp[43]]) as u64;
77                let ntp_frac = u32::from_be_bytes([resp[44], resp[45], resp[46], resp[47]]);
78                if ntp_secs < NTP_UNIX_EPOCH_DELTA {
79                    return Err(ShellError::ExecutionFailed);
80                }
81                let unix_secs = ntp_secs - NTP_UNIX_EPOCH_DELTA;
82                let unix_nanos = (((ntp_frac as u128) * 1_000_000_000u128) >> 32) as u32;
83                return Ok((unix_secs, unix_nanos));
84            }
85            Ok(_) => {}
86            Err(_) => {}
87        }
88
89        crate::process::yield_task();
90        let elapsed = crate::process::scheduler::ticks().wrapping_sub(start_tick);
91        if elapsed >= timeout_ticks {
92            let _ = vfs::close(fd);
93            return Err(ShellError::ExecutionFailed);
94        }
95    }
96}
97
98pub fn cmd_ntpdate(args: &[String]) -> Result<(), ShellError> {
99    let server = args.first().map(|s| s.as_str()).unwrap_or("pool.ntp.org");
100    shell_println!("ntpdate: querying {}...", server);
101
102    let server_ip = match resolve_ntp_server(server) {
103        Ok(ip) => ip,
104        Err(_) => {
105            shell_println!("  resolve failed for {}", server);
106            return Err(ShellError::ExecutionFailed);
107        }
108    };
109
110    match ntp_query(&server_ip) {
111        Ok((unix_secs, unix_nanos)) => {
112            shell_println!("  server: {}", format_ipv4(&server_ip));
113            shell_println!("  unix:   {}.{:09} UTC", unix_secs, unix_nanos);
114            shell_println!("  note: kernel realtime clock set is not implemented yet");
115            Ok(())
116        }
117        Err(_) => {
118            shell_println!(
119                "  no valid NTP response from {} ({})",
120                server,
121                format_ipv4(&server_ip)
122            );
123            Err(ShellError::ExecutionFailed)
124        }
125    }
126}