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 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
94pub 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
130const 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 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 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 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 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}