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    pub fn web_get_binary(
187        &self,
188        is_https: bool,
189        host: &str,
190        path: &str,
191    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
192        validate_web_request(host, path)?;
193        if is_https {
194            crate::kernel::tls::https_get_binary(host, path)
195                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
196        } else {
197            crate::kernel::net::http_get_binary(host, path)
198                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
199        }
200    }
201
202    /// 任意メソッド(GET/POST/PUT/DELETE/PATCH/HEAD 等)の HTTP/HTTPS リクエスト。
203    /// method は英大文字トークンへ正規化し、未知の文字は弾く(ヘッダインジェクション防止)。
204    /// is_https に応じて TLS 経路(tls::https_request)と平文経路(http_request_real)を振り分ける。
205    pub fn web_request(
206        &self,
207        is_https: bool,
208        host: &str,
209        path: &str,
210        method: &str,
211        content_type: &str,
212        body: &[u8],
213    ) -> Result<AppHttpResponse, TcpIpError> {
214        validate_web_request(host, path)?;
215        if body.len() > MAX_REQUEST_BODY {
216            return Err(TcpIpError::new(
217                TcpIpLayer::Application,
218                "request body too large",
219            ));
220        }
221        // メソッドは A-Z のみ・1..=16 バイトに制限(CR/LF やスペース混入を排除)。
222        let m = method.to_ascii_uppercase();
223        if m.is_empty() || m.len() > 16 || !m.bytes().all(|b| b.is_ascii_uppercase()) {
224            return Err(TcpIpError::new(
225                TcpIpLayer::Application,
226                "invalid HTTP method",
227            ));
228        }
229        if is_https {
230            // body を取り得るメソッドのみ Some を渡す(GET/HEAD/DELETE は None)。
231            let body_opt = if !body.is_empty() || m == "POST" || m == "PUT" || m == "PATCH" {
232                Some(body)
233            } else {
234                None
235            };
236            let resp =
237                crate::kernel::tls::https_request(&m, host, path, &[], content_type, body_opt)
238                    .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
239            Ok(AppHttpResponse {
240                remote_ip: resp.remote_ip,
241                local_port: resp.local_port,
242                status_code: resp.status_code,
243                reason: resp.reason,
244                headers: resp.headers,
245                body: resp.body,
246                tls: Some(TlsSessionMeta {
247                    version: resp.tls_version,
248                    cipher_suite: resp.cipher_suite,
249                    cert_subject: resp.cert_subject,
250                    cert_issuer: resp.cert_issuer,
251                    cert_not_after: resp.cert_not_after,
252                }),
253            })
254        } else {
255            let resp = crate::kernel::net::http_request_real(&m, host, path, content_type, body)
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: None,
265            })
266        }
267    }
268}
269
270impl LinkLayerApi for TcpIpStack {
271    fn link_up(&self) -> bool {
272        crate::kernel::net::status().link_up
273    }
274}
275
276impl InternetLayerApi for TcpIpStack {
277    fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError> {
278        crate::kernel::net::dns_query_a_via_udp(host)
279            .map_err(|e| TcpIpError::new(TcpIpLayer::Internet, e))
280    }
281}
282
283impl TransportLayerApi for TcpIpStack {
284    fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError> {
285        crate::kernel::net::tcp_connect_real(remote_ip, remote_port)
286            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
287    }
288
289    fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError> {
290        crate::kernel::net::tcp_send_real(local_port, data)
291            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
292    }
293
294    fn tcp_recv(
295        &self,
296        local_port: u16,
297        timeout_iters: usize,
298    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
299        crate::kernel::net::tcp_recv_real(local_port, timeout_iters)
300            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
301    }
302
303    fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError> {
304        crate::kernel::net::tcp_close_real(local_port)
305            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
306    }
307}
308
309impl ApplicationLayerApi for TcpIpStack {
310    fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
311        let resp = crate::kernel::net::http_get_real(host, path)
312            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
313        Ok(AppHttpResponse {
314            remote_ip: resp.remote_ip,
315            local_port: resp.local_port,
316            status_code: resp.status_code,
317            reason: resp.reason,
318            headers: resp.headers,
319            body: resp.body,
320            tls: None,
321        })
322    }
323
324    fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
325        let resp = crate::kernel::tls::https_get(host, path)
326            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
327        Ok(AppHttpResponse {
328            remote_ip: resp.remote_ip,
329            local_port: resp.local_port,
330            status_code: resp.status_code,
331            reason: resp.reason,
332            headers: resp.headers,
333            body: resp.body,
334            tls: Some(TlsSessionMeta {
335                version: resp.tls_version,
336                cipher_suite: resp.cipher_suite,
337                cert_subject: resp.cert_subject,
338                cert_issuer: resp.cert_issuer,
339                cert_not_after: resp.cert_not_after,
340            }),
341        })
342    }
343
344    fn http_post(
345        &self,
346        host: &str,
347        path: &str,
348        content_type: &str,
349        body: &[u8],
350    ) -> Result<AppHttpResponse, TcpIpError> {
351        let resp = crate::kernel::net::http_post_real(host, path, content_type, body)
352            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
353        Ok(AppHttpResponse {
354            remote_ip: resp.remote_ip,
355            local_port: resp.local_port,
356            status_code: resp.status_code,
357            reason: resp.reason,
358            headers: resp.headers,
359            body: resp.body,
360            tls: None,
361        })
362    }
363}