Skip to main content

strat9_kernel/shell/commands/util/
ntpdate.rs

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