Skip to main content

atmos/os_lib/aura/builtins/
os_net.rs

1// 分割: aura/builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ6)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4
5pub(crate) fn builtin_net(_env: &mut Env, _args: &[AST]) -> Result<Value, String> {
6    Ok(Value::Str(alloc::format!(
7        "NET:\n{}",
8        crate::kernel::net::status_line()
9    )))
10}
11
12pub(crate) fn builtin_nic(_env: &mut Env, _args: &[AST]) -> Result<Value, String> {
13    Ok(Value::Str(crate::kernel::net::nic_debug_info()))
14}
15
16pub(crate) fn builtin_dhcp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
17    let vals = eval_args(env, args)?;
18    let cmd = if vals.is_empty() {
19        String::from("show")
20    } else {
21        unquote(&vals[0])
22    };
23
24    match cmd.as_str() {
25        "show" => Ok(Value::Str(alloc::format!(
26            "DHCP: {}",
27            crate::kernel::net::dhcp_status_line()
28        ))),
29        "start" => match crate::kernel::net::dhcp_start_async(false) {
30            Ok(()) => Ok(Value::Str(String::from(
31                "DHCP start scheduled\nhint: run 'dhcp(\"show\")' to watch state",
32            ))),
33            Err(e) => Err(alloc::format!("DHCP error: {}", e)),
34        },
35        "renew" => match crate::kernel::net::dhcp_start_async(true) {
36            Ok(()) => Ok(Value::Str(String::from(
37                "DHCP renew scheduled\nhint: run 'dhcp(\"show\")' to watch state",
38            ))),
39            Err(e) => Err(alloc::format!("DHCP error: {}", e)),
40        },
41        "release" => {
42            crate::kernel::net::dhcp_release();
43            Ok(Value::Str(String::from("DHCP released")))
44        }
45        _ => Err(String::from(
46            "USAGE: dhcp([\"show\"|\"start\"|\"renew\"|\"release\"])",
47        )),
48    }
49}
50
51pub(crate) fn builtin_arp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
52    let vals = eval_args(env, args)?;
53    let cmd = if vals.is_empty() {
54        String::from("show")
55    } else {
56        unquote(&vals[0])
57    };
58
59    match cmd.as_str() {
60        "show" => {
61            let lines = crate::kernel::net::arp_cache_lines();
62            if lines.is_empty() {
63                Ok(Value::Str(String::from("ARP cache is empty")))
64            } else {
65                let mut out = String::from("ARP cache:\n");
66                for line in lines {
67                    out.push_str(&alloc::format!(" - {}\n", line));
68                }
69                Ok(Value::Str(out))
70            }
71        }
72        "whohas" => {
73            if vals.len() < 2 {
74                return Err(String::from("USAGE: arp(\"whohas\" \"ip\")"));
75            }
76            let ip_s = unquote(&vals[1]);
77            let target_ip = match crate::kernel::net::parse_ipv4(&ip_s) {
78                Some(ip) => ip,
79                None => return Err(String::from("ERROR: invalid IPv4 address")),
80            };
81            let mut frame = [0u8; 64];
82            match crate::kernel::net::build_arp_request(target_ip, &mut frame) {
83                Ok(len) => Ok(Value::Str(alloc::format!(
84                    "ARP who-has {}? request built ({} bytes)",
85                    crate::kernel::net::format_ipv4(target_ip),
86                    len
87                ))),
88                Err(e) => Err(alloc::format!("ARP error: {}", e)),
89            }
90        }
91        _ => Err(String::from(
92            "USAGE: arp([\"show\"|\"whohas\"] ...)",
93        )),
94    }
95}
96
97/// 【2026-07-29】ネットワークの成功率を実測するための自己診断。
98///
99/// `netbench("https://host/path", 10)` で同じ URL を 10 回取得し、
100/// 段階別の内訳(成功/DNS 失敗/接続失敗/ハンドシェイク失敗/打ち切り)を返す。
101///
102/// ブラウザ経由のページ読み込みで測ると、ページが早期に失敗した際に
103/// サンプルが 1〜2 件しか集まらず比較にならなかった
104/// (`retry_write` の効果検証がこれで頓挫した)。
105/// ここでは `https_get_binary` を直接叩くので、描画・レイアウト・
106/// キャッシュの影響を排除して**純粋にネットワークだけ**を測れる。
107pub(crate) fn builtin_netbench(env: &mut Env, args: &[AST]) -> Result<Value, String> {
108    let vals = eval_args(env, args)?;
109    if vals.is_empty() {
110        return Err(String::from("USAGE: netbench(\"https://host/path\", n)"));
111    }
112    let url = unquote(&vals[0]);
113    let n: u32 = if vals.len() > 1 {
114        unquote(&vals[1]).trim().parse().unwrap_or(5)
115    } else {
116        5
117    };
118    let n = n.clamp(1, 50);
119
120    let rest = url.strip_prefix("https://").unwrap_or(&url);
121    // 生スライスは UTF-8 の途中を切るとパニックするので `get` で安全に分割する
122    // (国際化ドメイン等でマルチバイトが混じり得る)。
123    let (host, path) = match rest.find('/') {
124        Some(i) => (
125            rest.get(..i).unwrap_or(rest),
126            rest.get(i..).unwrap_or("/"),
127        ),
128        None => (rest, "/"),
129    };
130
131    let mut ok = 0u32;
132    let mut bytes_total = 0usize;
133    // 【2026-07-29】第3引数で各回の間隔(秒)を指定できる。
134    // SLIRP のセッション枯渇が原因なら**時間を空ければ回復**するはず。
135    // 間隔 0 と 5 秒で成功率を比較すれば、時間依存かどうかが分かる。
136    let gap_s: u32 = if vals.len() > 2 {
137        unquote(&vals[2]).trim().parse().unwrap_or(0)
138    } else {
139        0
140    };
141
142    for i in 0..n {
143        if i > 0 && gap_s > 0 {
144            crate::kernel::scheduler::sleep((gap_s as usize) * 100);
145        }
146        // 【2026-07-29】各回の前にリソース状態を記録する。
147        // 連続取得で「成功 → 空応答 → TCP接続失敗 → DNS失敗」と
148        // 失敗段階が前倒しになる劣化パターンが観測されており、
149        // **どの資源が単調に減っていくか**を直接見るため。
150        crate::warn!(
151            "[NETBENCH][RES] before={} rx_frames={} eth(ipv4/arp/other/err)={:?} tcp_sockets={} udp_queue={} arp={}",
152            i + 1,
153            crate::kernel::usb::RX_FRAME_COUNT.load(core::sync::atomic::Ordering::Relaxed),
154            crate::kernel::net::arp::eth_dispatch_counts(),
155            crate::kernel::net::tcp_socket_lines().len(),
156            crate::kernel::net::udp_queue_lines(None).len(),
157            crate::kernel::net::arp_cache_lines().len()
158        );
159        match crate::kernel::tls::https_get_binary(host, path) {
160            Ok(b) => {
161                ok += 1;
162                bytes_total += b.len();
163                crate::warn!("[NETBENCH] {}/{} OK {} bytes", i + 1, n, b.len());
164            }
165            Err(e) => {
166                crate::warn!("[NETBENCH] {}/{} FAILED {}", i + 1, n, e);
167            }
168        }
169    }
170    crate::kernel::tls::stats::report("netbench-done");
171    Ok(Value::Str(alloc::format!(
172        "netbench {}{}: {}/{} ok, {} bytes total (段階別内訳は [NET][STATS] を参照)",
173        host, path, ok, n, bytes_total
174    )))
175}
176
177pub(crate) fn builtin_dns(env: &mut Env, args: &[AST]) -> Result<Value, String> {
178    let vals = eval_args(env, args)?;
179    if vals.is_empty() {
180        return Err(String::from("USAGE: dns(\"host\")"));
181    }
182    let host = unquote(&vals[0]);
183    match crate::kernel::net::dns_query_a_via_udp(&host) {
184        Ok(ip) => Ok(Value::Str(alloc::format!(
185            "DNS: {} -> {} (via UDP path)",
186            host,
187            crate::kernel::net::format_ipv4(ip)
188        ))),
189        Err(e) => Err(alloc::format!("DNS query error: {}", e)),
190    }
191}
192
193pub(crate) fn builtin_ntp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
194    let vals = eval_args(env, args)?;
195    // 引数があればサーバ指定、なければ NICT
196    let host = if vals.is_empty() {
197        String::from("ntp.nict.jp")
198    } else {
199        unquote(&vals[0])
200    };
201    match crate::kernel::net::ntp_query(&host) {
202        Ok(unix) => {
203            crate::kernel::timer::set_wall_clock(unix);
204            match crate::kernel::timer::format_datetime_jst() {
205                Some(dt) => Ok(Value::Str(alloc::format!(
206                    "NTP synced via {}: {} JST",
207                    host,
208                    dt
209                ))),
210                None => Ok(Value::Str(alloc::format!(
211                    "NTP synced via {}: unix={}",
212                    host,
213                    unix
214                ))),
215            }
216        }
217        Err(e) => Err(alloc::format!("NTP error: {}", e)),
218    }
219}
220
221pub(crate) fn builtin_date(env: &mut Env, args: &[AST]) -> Result<Value, String> {
222    let _ = eval_args(env, args)?;
223    match crate::kernel::timer::format_datetime_jst() {
224        Some(dt) => Ok(Value::Str(alloc::format!("{} JST", dt))),
225        None => Ok(Value::Str(String::from(
226            "Clock not synced. Run ntp() to sync via NTP.",
227        ))),
228    }
229}
230
231pub(crate) fn builtin_udp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
232    let vals = eval_args(env, args)?;
233    if vals.is_empty() {
234        return Err(String::from(
235            "USAGE: udp(\"recv\" port) | udp(\"send\" \"ip\" port \"msg\")",
236        ));
237    }
238    let cmd = unquote(&vals[0]);
239
240    let parse_port = |s: &str| -> Option<u16> { s.parse::<u16>().ok() };
241
242    match cmd.as_str() {
243        "recv" => {
244            if vals.len() < 2 {
245                return Err(String::from("USAGE: udp(\"recv\" port)"));
246            }
247            let port_s = unquote(&vals[1]);
248            let port = parse_port(&port_s).unwrap_or(0);
249            if let Some(line) = crate::kernel::net::udp_recv_next_line(port) {
250                Ok(Value::Str(line))
251            } else {
252                Ok(Value::Str(alloc::format!(
253                    "UDP RX queue empty on port {}",
254                    port
255                )))
256            }
257        }
258        "send" => {
259            if vals.len() < 4 {
260                return Err(String::from("USAGE: udp(\"send\" \"ip\" port \"msg\") or udp(\"send\" \"ip\" port (bytes...))"));
261            }
262            let ip_s = unquote(&vals[1]);
263            let port_s = unquote(&vals[2]);
264            let dst_ip = crate::kernel::net::parse_ipv4(&ip_s)
265                .ok_or_else(|| alloc::format!("invalid IPv4 address: {}", ip_s))?;
266            let port = parse_port(&port_s).unwrap_or(0);
267
268            let payload = match &vals[3] {
269                Value::Str(s) => {
270                    // 文字列の場合はそのままバイト列に変換
271                    s.as_bytes().to_vec()
272                }
273                Value::List(lst) => {
274                    // リストの場合は各要素を整数として扱う
275                    let mut bytes = Vec::new();
276                    for item in lst {
277                        match item {
278                            Value::Num(n) => {
279                                let byte_val = n
280                                    .to_i64()
281                                    .ok_or_else(|| String::from("Number out of range for byte"))?;
282                                if !(0..=255).contains(&byte_val) {
283                                    return Err(alloc::format!(
284                                        "Byte value {} out of range (0-255)",
285                                        byte_val
286                                    ));
287                                }
288                                bytes.push(byte_val as u8);
289                            }
290                            _ => return Err(String::from("List elements must be numbers (0-255)")),
291                        }
292                    }
293                    bytes
294                }
295                _ => return Err(String::from("Message must be a string or list of numbers")),
296            };
297
298            let len = payload.len();
299            match crate::kernel::net::send_udp_ipv4(dst_ip, 49152, port, &payload) {
300                Ok(sent) => {
301                    let display = match &vals[3] {
302                        Value::Str(_) => unquote(&vals[3]),
303                        Value::List(_) => alloc::format!("{} bytes", len),
304                        _ => String::from(""),
305                    };
306                    Ok(Value::Str(alloc::format!(
307                        "UDP TX: {} bytes -> {}:{} payload={}",
308                        sent,
309                        crate::kernel::net::format_ipv4(dst_ip),
310                        port,
311                        display
312                    )))
313                }
314                Err(e) => Err(alloc::format!("UDP error: {}", e)),
315            }
316        }
317        "queue" => {
318            let port_filter = if vals.len() > 1 {
319                parse_port(&unquote(&vals[1]))
320            } else {
321                None
322            };
323            let lines = crate::kernel::net::udp_queue_lines(port_filter);
324            if lines.is_empty() {
325                Ok(Value::Str(String::from("UDP RX queue is empty")))
326            } else {
327                let mut out = String::from("UDP RX queue:\n");
328                for line in lines {
329                    out.push_str(&alloc::format!(" - {}\n", line));
330                }
331                Ok(Value::Str(out))
332            }
333        }
334        _ => Err(String::from("Unknown udp command (inject command was removed)")),
335    }
336}
337
338pub(crate) fn builtin_tcp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
339    let vals = eval_args(env, args)?;
340    let cmd = if vals.is_empty() {
341        String::from("show")
342    } else {
343        unquote(&vals[0])
344    };
345    let parse_port = |s: &str| -> Option<u16> { s.parse::<u16>().ok() };
346
347    match cmd.as_str() {
348        "connect" => {
349            if vals.len() < 3 {
350                return Err(String::from("USAGE: tcp(\"connect\" \"ip\" port)"));
351            }
352            let remote_ip_s = unquote(&vals[1]);
353            let remote_ip = crate::kernel::net::parse_ipv4(&remote_ip_s)
354                .ok_or_else(|| alloc::format!("invalid IPv4 address: {}", remote_ip_s))?;
355            let remote_port = parse_port(&unquote(&vals[2])).unwrap_or(0);
356            match crate::kernel::net::tcp_connect_real(remote_ip, remote_port) {
357                Ok(local_port) => Ok(Value::Str(alloc::format!(
358                    "TCP connected: local {} -> {}:{}",
359                    local_port,
360                    crate::kernel::net::format_ipv4(remote_ip),
361                    remote_port
362                ))),
363                Err(e) => Err(alloc::format!("TCP connect error: {}", e)),
364            }
365        }
366        "close" => {
367            if vals.len() < 2 {
368                return Err(String::from("USAGE: tcp(\"close\" local_port)"));
369            }
370            let local_port = parse_port(&unquote(&vals[1])).unwrap_or(0);
371            match crate::kernel::net::tcp_close_real(local_port) {
372                Ok(()) => Ok(Value::Str(alloc::format!(
373                    "TCP closed: local {}",
374                    local_port
375                ))),
376                Err(e) => Err(alloc::format!("TCP close error: {}", e)),
377            }
378        }
379        "show" => {
380            let lines = crate::kernel::net::tcp_socket_lines();
381            if lines.is_empty() {
382                Ok(Value::Str(String::from("TCP socket table is empty")))
383            } else {
384                let mut out = String::from("TCP sockets:\n");
385                for line in lines {
386                    out.push_str(&alloc::format!(" - {}\n", line));
387                }
388                Ok(Value::Str(out))
389            }
390        }
391        _ => Err(String::from("Unknown tcp command")),
392    }
393}
394
395pub(crate) fn builtin_http(env: &mut Env, args: &[AST]) -> Result<Value, String> {
396    let vals = eval_args(env, args)?;
397    if vals.len() < 2 {
398        return Err(String::from(
399            "USAGE: http(\"get\"|\"post\" \"host\" [\"path\"] [\"content-type\" \"body\"])",
400        ));
401    }
402    let method = unquote(&vals[0]).to_lowercase();
403    let host = unquote(&vals[1]);
404    let path = if vals.len() > 2 {
405        unquote(&vals[2])
406    } else {
407        String::from("/")
408    };
409
410    if method == "post" {
411        let ct = if vals.len() > 3 {
412            unquote(&vals[3])
413        } else {
414            String::from("application/x-www-form-urlencoded")
415        };
416        let body_str = if vals.len() > 4 {
417            unquote(&vals[4])
418        } else {
419            String::new()
420        };
421        return match crate::kernel::net::http_post_real(&host, &path, &ct, body_str.as_bytes()) {
422            Ok(resp) => {
423                let body = if resp.body.len() > 96 {
424                    alloc::format!("{}...", head_str(&resp.body, 96))
425                } else {
426                    resp.body.clone()
427                };
428                Ok(Value::Str(alloc::format!(
429                    "HTTP POST {}{} -> {} status={} {}\nHTTP body: {}",
430                    host,
431                    path,
432                    crate::kernel::net::format_ipv4(resp.remote_ip),
433                    resp.status_code,
434                    resp.reason,
435                    body
436                )))
437            }
438            Err(e) => Err(alloc::format!("HTTP POST error: {}", e)),
439        };
440    }
441
442    match crate::kernel::net::http_get_real(&host, &path) {
443        Ok(resp) => {
444            let body = if resp.body.len() > 96 {
445                alloc::format!("{}...", head_str(&resp.body, 96))
446            } else {
447                resp.body.clone()
448            };
449            Ok(Value::Str(alloc::format!(
450                "HTTP GET {}{} -> {} status={} {}\nHTTP body: {}",
451                host,
452                path,
453                crate::kernel::net::format_ipv4(resp.remote_ip),
454                resp.status_code,
455                resp.reason,
456                body
457            )))
458        }
459        Err(e) => Err(alloc::format!("HTTP error: {}", e)),
460    }
461}
462
463pub(crate) fn builtin_https(env: &mut Env, args: &[AST]) -> Result<Value, String> {
464    let vals = eval_args(env, args)?;
465    if vals.len() < 2 || unquote(&vals[0]) != "get" {
466        return Err(String::from("USAGE: https(\"get\" \"host\" [\"path\"])"));
467    }
468    let host = unquote(&vals[1]);
469    let path = if vals.len() > 2 {
470        unquote(&vals[2])
471    } else {
472        String::from("/")
473    };
474
475    match crate::kernel::net::https_get_real(&host, &path) {
476        Ok(resp) => {
477            let body = if resp.body.len() > 96 {
478                alloc::format!("{}...", head_str(&resp.body, 96))
479            } else {
480                resp.body.clone()
481            };
482            Ok(Value::Str(alloc::format!("HTTPS GET {}{} -> {} tls={} cipher={} status={} {}\nHTTPS cert: subject='{}'\nHTTPS body: {}", host, path, crate::kernel::net::format_ipv4(resp.remote_ip), resp.tls_version, resp.cipher_suite, resp.status_code, resp.reason, resp.cert_subject, body)))
483        }
484        Err(e) => Err(alloc::format!("HTTPS error: {}", e)),
485    }
486}
487
488// ============ drive(Google Drive CUI) ============
489// 認証状態は drive アプリと同じ gdrive_auth.json を共有する(cloud::load_auth/save_auth)。
490// ログイン自体は drive アプリの device フロー UI で行う想定で、ここでは
491// 既にログイン済みのトークンを使った読み書きのみを CUI コマンドとして公開する。
492