Skip to main content

atmos/kernel/
net_stack.rs

1//! ネットワークスタックの階層抽象化トレイト(アプリ⇄カーネル境界の疎結合層)。
2//!
3//! [`kernel::net`] の具体実装(TCP/DNS/HTTP 等)を [`LinkLayerApi`]/[`InternetLayerApi`]/
4//! [`TransportLayerApi`]/[`ApplicationLayerApi`] としてラップし、`TcpIpStack` の
5//! `web_get`/`web_post`/`web_get_binary` を単一の入口として提供する。
6//!
7//! **設計意図**: アプリ(ブラウザ等)とカーネル内部(DNS/TCP/TLS/HTTP パーサ)を疎結合にし、
8//! この入口で host/path を検証する(`validate_web_request`)ことで、カーネル内部は
9//! 「検証済みの入力しか来ない」前提で実装できる。エラーは [`TcpIpError`](層情報付き)で
10//! 返り、不正入力でパニックしない。詳細は `spec/DESIGN.md` §2「API 境界の事前条件検証」参照。
11#![allow(dead_code)]
12
13use alloc::string::String;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum TcpIpLayer {
17    Link,
18    Internet,
19    Transport,
20    Application,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct TcpIpError {
25    pub layer: TcpIpLayer,
26    pub message: &'static str,
27}
28
29impl TcpIpError {
30    pub const fn new(layer: TcpIpLayer, message: &'static str) -> Self {
31        Self { layer, message }
32    }
33}
34
35pub trait LinkLayerApi {
36    fn link_up(&self) -> bool;
37}
38
39pub trait InternetLayerApi {
40    fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError>;
41}
42
43pub trait TransportLayerApi {
44    fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError>;
45    fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError>;
46    fn tcp_recv(
47        &self,
48        local_port: u16,
49        timeout_iters: usize,
50    ) -> Result<alloc::vec::Vec<u8>, TcpIpError>;
51    fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError>;
52}
53
54pub trait ApplicationLayerApi {
55    fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError>;
56    fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError>;
57    fn http_post(
58        &self,
59        host: &str,
60        path: &str,
61        content_type: &str,
62        body: &[u8],
63    ) -> Result<AppHttpResponse, TcpIpError>;
64}
65
66#[derive(Clone)]
67pub struct AppHttpResponse {
68    pub remote_ip: [u8; 4],
69    pub local_port: u16,
70    pub status_code: u16,
71    pub reason: String,
72    pub headers: alloc::collections::BTreeMap<String, String>,
73    pub body: String,
74    pub tls: Option<TlsSessionMeta>,
75}
76
77#[derive(Clone)]
78pub struct TlsSessionMeta {
79    pub version: String,
80    pub cipher_suite: String,
81    pub cert_subject: String,
82    pub cert_issuer: String,
83    pub cert_not_after: String,
84}
85
86pub struct TcpIpStack;
87
88impl Default for TcpIpStack {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94/// アプリ⇄カーネルのネットワーク API 境界での事前条件検証。
95///
96/// 方針: アプリからの要求はすべてこの関数を通った後にカーネル内部(DNS/TCP/TLS/HTTP パーサ)
97/// へ渡す。これにより内部関数は「検証済みの host/path しか来ない」前提で動ける(疎結合)。
98/// パニックではなく `Result` で弾くため、不正入力で OS が落ちることはない。
99/// - host: 1..=253 バイト、`A-Za-z0-9.-` のみ(空白・制御文字・CR/LF を排除)
100/// - path: `/` 始まり、1..=4096 バイト、制御文字・CR/LF を含まない
101///   (CR/LF 混入による HTTP ヘッダインジェクションを防ぐ)
102pub fn validate_web_request(host: &str, path: &str) -> Result<(), TcpIpError> {
103    if host.is_empty() || host.len() > 253 {
104        return Err(TcpIpError::new(
105            TcpIpLayer::Application,
106            "invalid host length",
107        ));
108    }
109    if !host
110        .bytes()
111        .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'-')
112    {
113        return Err(TcpIpError::new(
114            TcpIpLayer::Application,
115            "invalid host characters",
116        ));
117    }
118    if !path.starts_with('/') || path.len() > 4096 {
119        return Err(TcpIpError::new(TcpIpLayer::Application, "invalid path"));
120    }
121    if path.bytes().any(|b| b < 0x20 || b == 0x7f) {
122        return Err(TcpIpError::new(
123            TcpIpLayer::Application,
124            "path contains control characters",
125        ));
126    }
127    Ok(())
128}
129
130/// POST ボディの上限(メモリ枯渇・異常要求の防御)。
131const MAX_REQUEST_BODY: usize = 8 * 1024 * 1024;
132
133impl TcpIpStack {
134    pub const fn new() -> Self {
135        Self
136    }
137
138    pub fn web_get(
139        &self,
140        is_https: bool,
141        host: &str,
142        path: &str,
143    ) -> Result<AppHttpResponse, TcpIpError> {
144        validate_web_request(host, path)?;
145        if is_https {
146            self.https_get(host, path)
147        } else {
148            self.http_get(host, path)
149        }
150    }
151
152    /// is_https に応じて HTTPS(TLS) / HTTP の POST を振り分ける。
153    /// HTTPS フォーム送信が平文 HTTP に落ちてボディが届かない不具合を防ぐ。
154    pub fn web_post(
155        &self,
156        is_https: bool,
157        host: &str,
158        path: &str,
159        content_type: &str,
160        body: &[u8],
161    ) -> Result<AppHttpResponse, TcpIpError> {
162        validate_web_request(host, path)?;
163        if body.len() > MAX_REQUEST_BODY {
164            return Err(TcpIpError::new(
165                TcpIpLayer::Application,
166                "request body too large",
167            ));
168        }
169        if is_https {
170            let resp = crate::kernel::tls::https_post(host, path, content_type, body)
171                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
172            Ok(AppHttpResponse {
173                remote_ip: resp.remote_ip,
174                local_port: resp.local_port,
175                status_code: resp.status_code,
176                reason: resp.reason,
177                headers: resp.headers,
178                body: resp.body,
179                tls: None,
180            })
181        } else {
182            self.http_post(host, path, content_type, body)
183        }
184    }
185
186    /// `web_get_binary` の厳格版(HTTPS で途中切れの応答を Err にする)。画像用。
187    /// 理由は `tls::https_get_binary_complete` を参照。
188    pub fn web_get_binary_complete(
189        &self,
190        is_https: bool,
191        host: &str,
192        path: &str,
193    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
194        validate_web_request(host, path)?;
195        if is_https {
196            crate::kernel::tls::https_get_binary_complete(host, path)
197                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
198        } else {
199            crate::kernel::net::http_get_binary(host, path)
200                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
201        }
202    }
203
204    pub fn web_get_binary(
205        &self,
206        is_https: bool,
207        host: &str,
208        path: &str,
209    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
210        validate_web_request(host, path)?;
211        if is_https {
212            crate::kernel::tls::https_get_binary(host, path)
213                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
214        } else {
215            crate::kernel::net::http_get_binary(host, path)
216                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
217        }
218    }
219
220    /// 任意メソッド(GET/POST/PUT/DELETE/PATCH/HEAD 等)の HTTP/HTTPS リクエスト。
221    /// method は英大文字トークンへ正規化し、未知の文字は弾く(ヘッダインジェクション防止)。
222    /// is_https に応じて TLS 経路(tls::https_request)と平文経路(http_request_real)を振り分ける。
223    pub fn web_request(
224        &self,
225        is_https: bool,
226        host: &str,
227        path: &str,
228        method: &str,
229        content_type: &str,
230        body: &[u8],
231    ) -> Result<AppHttpResponse, TcpIpError> {
232        validate_web_request(host, path)?;
233        if body.len() > MAX_REQUEST_BODY {
234            return Err(TcpIpError::new(
235                TcpIpLayer::Application,
236                "request body too large",
237            ));
238        }
239        // メソッドは A-Z のみ・1..=16 バイトに制限(CR/LF やスペース混入を排除)。
240        let m = method.to_ascii_uppercase();
241        if m.is_empty() || m.len() > 16 || !m.bytes().all(|b| b.is_ascii_uppercase()) {
242            return Err(TcpIpError::new(
243                TcpIpLayer::Application,
244                "invalid HTTP method",
245            ));
246        }
247        if is_https {
248            // body を取り得るメソッドのみ Some を渡す(GET/HEAD/DELETE は None)。
249            let body_opt = if !body.is_empty() || m == "POST" || m == "PUT" || m == "PATCH" {
250                Some(body)
251            } else {
252                None
253            };
254            let resp =
255                crate::kernel::tls::https_request(&m, host, path, &[], content_type, body_opt)
256                    .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
257            Ok(AppHttpResponse {
258                remote_ip: resp.remote_ip,
259                local_port: resp.local_port,
260                status_code: resp.status_code,
261                reason: resp.reason,
262                headers: resp.headers,
263                body: resp.body,
264                tls: Some(TlsSessionMeta {
265                    version: resp.tls_version,
266                    cipher_suite: resp.cipher_suite,
267                    cert_subject: resp.cert_subject,
268                    cert_issuer: resp.cert_issuer,
269                    cert_not_after: resp.cert_not_after,
270                }),
271            })
272        } else {
273            let resp = crate::kernel::net::http_request_real(&m, host, path, content_type, body)
274                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
275            Ok(AppHttpResponse {
276                remote_ip: resp.remote_ip,
277                local_port: resp.local_port,
278                status_code: resp.status_code,
279                reason: resp.reason,
280                headers: resp.headers,
281                body: resp.body,
282                tls: None,
283            })
284        }
285    }
286}
287
288impl LinkLayerApi for TcpIpStack {
289    fn link_up(&self) -> bool {
290        crate::kernel::net::status().link_up
291    }
292}
293
294impl InternetLayerApi for TcpIpStack {
295    fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError> {
296        crate::kernel::net::dns_query_a_via_udp(host)
297            .map_err(|e| TcpIpError::new(TcpIpLayer::Internet, e))
298    }
299}
300
301impl TransportLayerApi for TcpIpStack {
302    fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError> {
303        crate::kernel::net::tcp_connect_real(remote_ip, remote_port)
304            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
305    }
306
307    fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError> {
308        crate::kernel::net::tcp_send_real(local_port, data)
309            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
310    }
311
312    fn tcp_recv(
313        &self,
314        local_port: u16,
315        timeout_iters: usize,
316    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
317        crate::kernel::net::tcp_recv_real(local_port, timeout_iters)
318            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
319    }
320
321    fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError> {
322        crate::kernel::net::tcp_close_real(local_port)
323            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
324    }
325}
326
327impl ApplicationLayerApi for TcpIpStack {
328    fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
329        let resp = crate::kernel::net::http_get_real(host, path)
330            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
331        Ok(AppHttpResponse {
332            remote_ip: resp.remote_ip,
333            local_port: resp.local_port,
334            status_code: resp.status_code,
335            reason: resp.reason,
336            headers: resp.headers,
337            body: resp.body,
338            tls: None,
339        })
340    }
341
342    fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
343        let resp = crate::kernel::tls::https_get(host, path)
344            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
345        Ok(AppHttpResponse {
346            remote_ip: resp.remote_ip,
347            local_port: resp.local_port,
348            status_code: resp.status_code,
349            reason: resp.reason,
350            headers: resp.headers,
351            body: resp.body,
352            tls: Some(TlsSessionMeta {
353                version: resp.tls_version,
354                cipher_suite: resp.cipher_suite,
355                cert_subject: resp.cert_subject,
356                cert_issuer: resp.cert_issuer,
357                cert_not_after: resp.cert_not_after,
358            }),
359        })
360    }
361
362    fn http_post(
363        &self,
364        host: &str,
365        path: &str,
366        content_type: &str,
367        body: &[u8],
368    ) -> Result<AppHttpResponse, TcpIpError> {
369        let resp = crate::kernel::net::http_post_real(host, path, content_type, body)
370            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
371        Ok(AppHttpResponse {
372            remote_ip: resp.remote_ip,
373            local_port: resp.local_port,
374            status_code: resp.status_code,
375            reason: resp.reason,
376            headers: resp.headers,
377            body: resp.body,
378            tls: None,
379        })
380    }
381}