1#![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 body_bytes: alloc::vec::Vec<u8>,
76 pub tls: Option<TlsSessionMeta>,
77}
78
79#[derive(Clone)]
80pub struct TlsSessionMeta {
81 pub version: String,
82 pub cipher_suite: String,
83 pub cert_subject: String,
84 pub cert_issuer: String,
85 pub cert_not_after: String,
86}
87
88pub struct TcpIpStack;
89
90impl Default for TcpIpStack {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96pub fn validate_web_request(host: &str, path: &str) -> Result<(), TcpIpError> {
107 if !crate::kernel::net::addr_util::is_valid_request_host(host) {
108 return Err(TcpIpError::new(
109 TcpIpLayer::Application,
110 "invalid host",
111 ));
112 }
113 if !path.starts_with('/') || path.len() > 4096 {
114 return Err(TcpIpError::new(TcpIpLayer::Application, "invalid path"));
115 }
116 if path.bytes().any(|b| b < 0x20 || b == 0x7f) {
117 return Err(TcpIpError::new(
118 TcpIpLayer::Application,
119 "path contains control characters",
120 ));
121 }
122 Ok(())
123}
124
125const MAX_REQUEST_BODY: usize = 8 * 1024 * 1024;
127
128impl TcpIpStack {
129 pub const fn new() -> Self {
130 Self
131 }
132
133 pub fn web_get(
134 &self,
135 is_https: bool,
136 host: &str,
137 path: &str,
138 ) -> Result<AppHttpResponse, TcpIpError> {
139 self.web_get_with_referer(is_https, host, path, None)
140 }
141
142 pub fn web_get_with_referer(
144 &self,
145 is_https: bool,
146 host: &str,
147 path: &str,
148 referer: Option<&str>,
149 ) -> Result<AppHttpResponse, TcpIpError> {
150 validate_web_request(host, path)?;
151 if is_https {
152 self.https_get_ref(host, path, referer)
153 } else {
154 self.http_get_ref(host, path, referer)
155 }
156 }
157
158 fn http_get_ref(&self, host: &str, path: &str, referer: Option<&str>) -> Result<AppHttpResponse, TcpIpError> {
159 let resp = crate::kernel::net::http_get_real_with_referer(host, path, referer)
160 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
161 Ok(AppHttpResponse {
162 remote_ip: resp.remote_ip,
163 local_port: resp.local_port,
164 status_code: resp.status_code,
165 reason: resp.reason,
166 headers: resp.headers,
167 body: resp.body,
168 body_bytes: resp.body_bytes,
169 tls: None,
170 })
171 }
172
173 fn https_get_ref(&self, host: &str, path: &str, referer: Option<&str>) -> Result<AppHttpResponse, TcpIpError> {
174 let resp = crate::kernel::tls::https_get_with_referer(host, path, referer)
175 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
176 Ok(AppHttpResponse {
177 remote_ip: resp.remote_ip,
178 local_port: resp.local_port,
179 status_code: resp.status_code,
180 reason: resp.reason,
181 headers: resp.headers,
182 body: resp.body,
183 body_bytes: resp.body_bytes,
184 tls: Some(TlsSessionMeta {
185 version: resp.tls_version,
186 cipher_suite: resp.cipher_suite,
187 cert_subject: resp.cert_subject,
188 cert_issuer: resp.cert_issuer,
189 cert_not_after: resp.cert_not_after,
190 }),
191 })
192 }
193
194 pub fn web_post(
197 &self,
198 is_https: bool,
199 host: &str,
200 path: &str,
201 content_type: &str,
202 body: &[u8],
203 ) -> Result<AppHttpResponse, TcpIpError> {
204 validate_web_request(host, path)?;
205 if body.len() > MAX_REQUEST_BODY {
206 return Err(TcpIpError::new(
207 TcpIpLayer::Application,
208 "request body too large",
209 ));
210 }
211 if is_https {
212 let resp = crate::kernel::tls::https_post(host, path, content_type, body)
213 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
214 Ok(AppHttpResponse {
215 remote_ip: resp.remote_ip,
216 local_port: resp.local_port,
217 status_code: resp.status_code,
218 reason: resp.reason,
219 headers: resp.headers,
220 body: resp.body,
221 body_bytes: resp.body_bytes,
222 tls: None,
223 })
224 } else {
225 self.http_post(host, path, content_type, body)
226 }
227 }
228
229 pub fn web_get_binary_complete(
232 &self,
233 is_https: bool,
234 host: &str,
235 path: &str,
236 ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
237 validate_web_request(host, path)?;
238 if is_https {
239 crate::kernel::tls::https_get_binary_complete(host, path)
240 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
241 } else {
242 crate::kernel::net::http_get_binary(host, path)
243 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
244 }
245 }
246
247 pub fn web_get_binary(
248 &self,
249 is_https: bool,
250 host: &str,
251 path: &str,
252 ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
253 validate_web_request(host, path)?;
254 if is_https {
255 crate::kernel::tls::https_get_binary(host, path)
256 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
257 } else {
258 crate::kernel::net::http_get_binary(host, path)
259 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
260 }
261 }
262
263 pub fn web_request(
267 &self,
268 is_https: bool,
269 host: &str,
270 path: &str,
271 method: &str,
272 content_type: &str,
273 body: &[u8],
274 headers: &[(&str, &str)],
275 ) -> Result<AppHttpResponse, TcpIpError> {
276 validate_web_request(host, path)?;
277 if body.len() > MAX_REQUEST_BODY {
278 return Err(TcpIpError::new(
279 TcpIpLayer::Application,
280 "request body too large",
281 ));
282 }
283 let m = method.to_ascii_uppercase();
285 if m.is_empty() || m.len() > 16 || !m.bytes().all(|b| b.is_ascii_uppercase()) {
286 return Err(TcpIpError::new(
287 TcpIpLayer::Application,
288 "invalid HTTP method",
289 ));
290 }
291 if is_https {
292 let body_opt = if !body.is_empty() || m == "POST" || m == "PUT" || m == "PATCH" {
294 Some(body)
295 } else {
296 None
297 };
298 let resp =
299 crate::kernel::tls::https_request(&m, host, path, headers, content_type, body_opt)
300 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
301 Ok(AppHttpResponse {
302 remote_ip: resp.remote_ip,
303 local_port: resp.local_port,
304 status_code: resp.status_code,
305 reason: resp.reason,
306 headers: resp.headers,
307 body: resp.body,
308 body_bytes: resp.body_bytes,
309 tls: Some(TlsSessionMeta {
310 version: resp.tls_version,
311 cipher_suite: resp.cipher_suite,
312 cert_subject: resp.cert_subject,
313 cert_issuer: resp.cert_issuer,
314 cert_not_after: resp.cert_not_after,
315 }),
316 })
317 } else {
318 let resp = crate::kernel::net::http_request_real(&m, host, path, content_type, body)
319 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
320 Ok(AppHttpResponse {
321 remote_ip: resp.remote_ip,
322 local_port: resp.local_port,
323 status_code: resp.status_code,
324 reason: resp.reason,
325 headers: resp.headers,
326 body: resp.body,
327 body_bytes: resp.body_bytes,
328 tls: None,
329 })
330 }
331 }
332}
333
334impl LinkLayerApi for TcpIpStack {
335 fn link_up(&self) -> bool {
336 crate::kernel::net::status().link_up
337 }
338}
339
340impl InternetLayerApi for TcpIpStack {
341 fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError> {
342 crate::kernel::net::dns_query_a_via_udp(host)
343 .map_err(|e| TcpIpError::new(TcpIpLayer::Internet, e))
344 }
345}
346
347impl TransportLayerApi for TcpIpStack {
348 fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError> {
349 crate::kernel::net::tcp_connect_real(remote_ip, remote_port)
350 .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
351 }
352
353 fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError> {
354 crate::kernel::net::tcp_send_real(local_port, data)
355 .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
356 }
357
358 fn tcp_recv(
359 &self,
360 local_port: u16,
361 timeout_iters: usize,
362 ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
363 crate::kernel::net::tcp_recv_real(local_port, timeout_iters)
364 .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
365 }
366
367 fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError> {
368 crate::kernel::net::tcp_close_real(local_port)
369 .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
370 }
371}
372
373impl ApplicationLayerApi for TcpIpStack {
374 fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
375 self.http_get_ref(host, path, None)
376 }
377
378 fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
379 self.https_get_ref(host, path, None)
380 }
381
382 fn http_post(
383 &self,
384 host: &str,
385 path: &str,
386 content_type: &str,
387 body: &[u8],
388 ) -> Result<AppHttpResponse, TcpIpError> {
389 let resp = crate::kernel::net::http_post_real(host, path, content_type, body)
390 .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
391 Ok(AppHttpResponse {
392 remote_ip: resp.remote_ip,
393 local_port: resp.local_port,
394 status_code: resp.status_code,
395 reason: resp.reason,
396 headers: resp.headers,
397 body: resp.body,
398 body_bytes: resp.body_bytes,
399 tls: None,
400 })
401 }
402}