Skip to main content

atmos/kernel/net/
addr_util.rs

1// L2/L3アドレス表現・チェックサムの純粋関数群。
2//
3// ハードウェア(NIC)やグローバル状態(`super::TCP_SOCKETS`/`NET_LOCK`)に
4// 一切依存しない自己完結モジュールとして`net/mod.rs`から切り出した
5// (`os_lib/web_engine/url_resolve.rs`と同じ狙い)。これによりホスト側
6// ユニットテスト(`tests/`, `x86_64-pc-windows-msvc`ターゲット, `make test`)
7// から`#[path]`で直接取り込んで検証できる。
8
9use alloc::string::String;
10
11/// アプリからの Web 要求の host 部(`名前[:ポート]`)が妥当か。
12///
13/// 名前は 1..=253 バイトの `A-Za-z0-9.-`(空白・制御文字・CR/LF を排除し、
14/// HTTP ヘッダインジェクションを防ぐ)。ポートは 1..=65535 の 10 進数。
15/// 【2026-10-09】従来はポートを許さず、`http://10.0.2.2:8765/` が開けなかった。
16pub fn is_valid_request_host(host: &str) -> bool {
17    let (name, port) = match host.split_once(':') {
18        Some((n, p)) => (n, Some(p)),
19        None => (host, None),
20    };
21    let name_ok = !name.is_empty()
22        && name.len() <= 253
23        && name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'-');
24    let port_ok = match port {
25        None => true,
26        Some(p) => {
27            !p.is_empty()
28                && p.len() <= 5
29                && p.bytes().all(|b| b.is_ascii_digit())
30                && p.parse::<u32>().is_ok_and(|v| (1..=65535).contains(&v))
31        }
32    };
33    name_ok && port_ok
34}
35
36/// host 文字列(`ホスト名[:ポート]`)をホスト名とポート番号に分解する。
37/// ポート番号が省略されている場合、または 0 や不正な値の場合は `default_port` を返す。
38pub fn parse_host_port_with_default(host: &str, default_port: u16) -> (&str, u16) {
39    if let Some((h, p_str)) = host.split_once(':') {
40        if let Ok(p) = p_str.parse::<u16>() {
41            if p > 0 {
42                return (h, p);
43            }
44        }
45    }
46    (host, default_port)
47}
48
49/// host 文字列(`ホスト名[:ポート]`)をホスト名とポート番号に分解する(HTTP既定: 80)。
50pub fn parse_host_port(host: &str) -> (&str, u16) {
51    parse_host_port_with_default(host, 80)
52}
53
54pub fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
55    let mut out = [0u8; 4];
56    let mut part_idx = 0usize;
57    let mut cur: u16 = 0;
58    let mut has_digit = false;
59
60    for ch in s.chars() {
61        if ch == '.' {
62            if !has_digit || part_idx >= 4 || cur > 255 {
63                return None;
64            }
65            out[part_idx] = cur as u8;
66            part_idx += 1;
67            cur = 0;
68            has_digit = false;
69            continue;
70        }
71
72        if !ch.is_ascii_digit() {
73            return None;
74        }
75
76        cur = cur
77            .saturating_mul(10)
78            .saturating_add((ch as u16).saturating_sub('0' as u16));
79        if cur > 255 {
80            return None;
81        }
82        has_digit = true;
83    }
84
85    if !has_digit || part_idx != 3 {
86        return None;
87    }
88
89    out[3] = cur as u8;
90    Some(out)
91}
92
93pub fn parse_mac(s: &str) -> Option<[u8; 6]> {
94    let mut mac = [0u8; 6];
95    let mut idx = 0usize;
96
97    for part in s.split(':') {
98        if idx >= 6 || part.len() != 2 {
99            return None;
100        }
101
102        let bytes = part.as_bytes();
103        let hi = hex_nibble(bytes[0])?;
104        let lo = hex_nibble(bytes[1])?;
105        mac[idx] = (hi << 4) | lo;
106        idx += 1;
107    }
108
109    if idx != 6 {
110        return None;
111    }
112
113    Some(mac)
114}
115
116pub fn format_ipv4(ip: [u8; 4]) -> String {
117    alloc::format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])
118}
119
120pub fn format_mac(mac: [u8; 6]) -> String {
121    alloc::format!(
122        "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
123        mac[0],
124        mac[1],
125        mac[2],
126        mac[3],
127        mac[4],
128        mac[5]
129    )
130}
131
132pub(crate) fn ipv4_header_checksum(header: &[u8]) -> u16 {
133    internet_checksum(header)
134}
135
136/// RFC 1071 インターネットチェックサム(IPv4ヘッダ/ICMP/UDP擬似ヘッダ共通)。
137pub(crate) fn internet_checksum(data: &[u8]) -> u16 {
138    let mut sum: u32 = 0;
139    let mut i = 0usize;
140
141    while i + 1 < data.len() {
142        let word = ((data[i] as u16) << 8) | data[i + 1] as u16;
143        sum = sum.wrapping_add(word as u32);
144        i += 2;
145    }
146
147    if i < data.len() {
148        let word = (data[i] as u16) << 8;
149        sum = sum.wrapping_add(word as u32);
150    }
151
152    while (sum >> 16) != 0 {
153        sum = (sum & 0xFFFF) + (sum >> 16);
154    }
155
156    !(sum as u16)
157}
158
159fn hex_nibble(c: u8) -> Option<u8> {
160    match c {
161        b'0'..=b'9' => Some(c - b'0'),
162        b'a'..=b'f' => Some(c - b'a' + 10),
163        b'A'..=b'F' => Some(c - b'A' + 10),
164        _ => None,
165    }
166}