1use alloc::string::String;
17use alloc::vec::Vec;
18
19use crate::kernel::reclock::RecoverableMutex;
20
21static HTTPS_ATTEMPT_ORDINAL: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
27
28pub fn next_https_attempt_ordinal() -> u32 {
29 HTTPS_ATTEMPT_ORDINAL.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
30}
31
32pub(crate) mod addr_util;
40pub(crate) mod arp;
41pub(crate) mod arp_cache;
42pub(crate) mod dhcp;
43pub(crate) mod dns;
44pub(crate) mod http;
45pub(crate) mod icmp;
46pub(crate) mod tcp;
47pub(crate) mod tcp_state;
48pub(crate) mod udp;
49pub(crate) mod eth_frame;
50pub(crate) mod tx_queue;
51pub(crate) mod reassembly;
52pub(crate) mod udp_queue;
53
54pub use addr_util::{format_ipv4, format_mac, parse_ipv4, parse_mac};
55pub(crate) use addr_util::{internet_checksum, ipv4_header_checksum};
56
57pub use arp::{
59 arp_cache_lines, build_arp_request, get_next_hop_ip, ingest_ethernet_frame,
60 ingest_nic_ethernet_frame, ingest_nic_ipv4_packet,
61 nic_pop_tx_ipv4_packet, nic_take_tx_ipv4_chunk, transmit_nic_ethernet_frame,
62 transmit_nic_ipv4_packet,
63};
64pub use dhcp::{
65 dhcp_acquire, dhcp_release, dhcp_start_async, dhcp_status_line, dhcp_tick,
66 set_dhcp_auto_pending,
67};
68pub use dns::{
69 build_dns_query, dns_query_a_via_udp, ntp_query, ntp_sync, parse_dns_a_response,
70};
71pub use http::{
72 extract_http_body_binary, http_get_binary, http_get_real, http_post_real, http_request_real,
73 https_get_real, parse_http_response, parse_http_response_bytes,
74};
75pub use icmp::{
76 build_icmp_echo_request, parse_icmp_echo_reply_ipv4, ping_once_real,
77};
78pub use tcp::{
79 tcp_close_real, tcp_connect_real, tcp_current_snd_seq, tcp_is_socket_closed,
80 tcp_recv_peek_nonempty, tcp_recv_real, tcp_retransmit_real, tcp_send_real, tcp_socket_lines,
81};
82pub use udp::{
83 send_udp_ipv4, udp_queue_lines, udp_recv_next_datagram,
84 udp_recv_next_line,
85};
86
87pub(crate) static NET_LOCK: RecoverableMutex<()> = RecoverableMutex::new(());
89
90pub fn force_release_locks(cid: i16) {
91 unsafe {
92 NET_LOCK.force_release_if_core(cid);
93 }
94}
95
96#[derive(Copy, Clone)]
98pub struct NetworkState {
99 pub link_up: bool,
100 pub nic_backend: NicBackend,
101 pub mac: [u8; 6],
102 pub ip: [u8; 4],
103 pub gateway: [u8; 4],
104 pub dns: [u8; 4],
105 pub dns2: [u8; 4],
106 pub usb_eth_present: bool,
107 pub usb_eth_addr: u8,
108 pub usb_eth_bulk_in: u8,
109 pub usb_eth_bulk_out: u8,
110 pub usb_eth_mps: u16,
111 pub usb_eth_is_rndis: bool,
112 pub tx_packets: u64,
113 pub rx_packets: u64,
114 pub raw_rx_frames: u64,
118 pub dhcp_active: bool,
119 pub dhcp_server: [u8; 4],
120 pub dhcp_lease_secs: u32,
121 pub dhcp_subnet: [u8; 4],
122 pub dhcp_t1_secs: u32,
123 pub dhcp_t2_secs: u32,
124}
125
126#[derive(Copy, Clone)]
127pub enum NicBackend {
128 SoftwareLoopback,
129 UsbEthernet,
130}
131
132pub use arp_cache::ArpEntry;
136
137#[derive(Copy, Clone)]
138pub struct PingReply {
139 pub src_ip: [u8; 4],
140 pub identifier: u16,
141 pub sequence: u16,
142 pub payload_len: usize,
143}
144
145const ARP_CACHE_SIZE: usize = 16;
146static mut ARP_CACHE: [ArpEntry; ARP_CACHE_SIZE] = [ArpEntry::empty(); ARP_CACHE_SIZE];
147static mut ARP_TICK: u32 = 1;
148static mut ARP_NEG_IP: [u8; 4] = [0; 4];
153static mut ARP_NEG_UNTIL_MS: u64 = 0;
154static mut PING_SEQ: u16 = 1;
155static DNS_TXID: core::sync::atomic::AtomicU16 = core::sync::atomic::AtomicU16::new(0x1200);
158static DNS_SRC_PORT: core::sync::atomic::AtomicU16 =
162 core::sync::atomic::AtomicU16::new(DNS_SRC_PORT_BASE);
163pub(super) const DNS_SRC_PORT_BASE: u16 = 53000;
164pub(super) const DNS_SRC_PORT_RANGE: u16 = 512; const DNS_CACHE_SIZE: usize = 32;
169const DNS_CACHE_NAME_MAX: usize = 64;
170const DNS_CACHE_TTL_MS: u64 = 60_000; #[derive(Copy, Clone)]
173struct DnsCacheEntry {
174 valid: bool,
175 name: [u8; DNS_CACHE_NAME_MAX],
176 name_len: usize,
177 ip: [u8; 4],
178 expires_ms: u64,
179}
180
181impl DnsCacheEntry {
182 const fn empty() -> Self {
183 Self {
184 valid: false,
185 name: [0; DNS_CACHE_NAME_MAX],
186 name_len: 0,
187 ip: [0; 4],
188 expires_ms: 0,
189 }
190 }
191}
192
193static mut DNS_CACHE: [DnsCacheEntry; DNS_CACHE_SIZE] = [DnsCacheEntry::empty(); DNS_CACHE_SIZE];
194
195pub(crate) fn next_dns_src_port() -> u16 {
197 use core::sync::atomic::Ordering;
198 let raw = DNS_SRC_PORT.fetch_add(1, Ordering::Relaxed);
200 if !(DNS_SRC_PORT_BASE..DNS_SRC_PORT_BASE + DNS_SRC_PORT_RANGE).contains(&raw) {
201 DNS_SRC_PORT.store(DNS_SRC_PORT_BASE + 1, Ordering::Relaxed);
202 return DNS_SRC_PORT_BASE;
203 }
204 raw
205}
206
207pub(crate) fn next_dns_txid() -> u16 {
209 DNS_TXID.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
210}
211
212pub(crate) fn dns_cache_lookup(name: &str) -> Option<[u8; 4]> {
214 let nb = name.as_bytes();
215 if nb.len() > DNS_CACHE_NAME_MAX {
216 return None;
217 }
218 let now = crate::kernel::timer::get_system_time_ms();
219 unsafe {
220 for e in DNS_CACHE.iter() {
221 if e.valid
222 && e.name_len == nb.len()
223 && &e.name[..e.name_len] == nb
224 && now < e.expires_ms
225 {
226 return Some(e.ip);
227 }
228 }
229 }
230 None
231}
232
233pub(crate) fn dns_cache_insert(name: &str, ip: [u8; 4]) {
235 let nb = name.as_bytes();
236 if nb.is_empty() || nb.len() > DNS_CACHE_NAME_MAX {
237 return;
238 }
239 let now = crate::kernel::timer::get_system_time_ms();
240 let expires_ms = now.saturating_add(DNS_CACHE_TTL_MS);
241 unsafe {
242 let mut free_idx: Option<usize> = None;
244 let mut oldest_idx = 0usize;
245 let mut oldest_exp = u64::MAX;
246 for (i, e) in DNS_CACHE.iter().enumerate() {
247 if e.valid && e.name_len == nb.len() && &e.name[..e.name_len] == nb {
248 free_idx = Some(i);
249 break;
250 }
251 if free_idx.is_none() && (!e.valid || now >= e.expires_ms) {
252 free_idx = Some(i);
253 }
254 if e.expires_ms < oldest_exp {
255 oldest_exp = e.expires_ms;
256 oldest_idx = i;
257 }
258 }
259 let idx = free_idx.unwrap_or(oldest_idx);
260 let mut entry = DnsCacheEntry::empty();
261 entry.valid = true;
262 entry.name[..nb.len()].copy_from_slice(nb);
263 entry.name_len = nb.len();
264 entry.ip = ip;
265 entry.expires_ms = expires_ms;
266 DNS_CACHE[idx] = entry;
267 }
268}
269
270const UDP_RX_QUEUE_SIZE: usize = 32;
271const TCP_SOCKET_TABLE_SIZE: usize = 32;
278const NIC_TX_QUEUE_SIZE: usize = 64;
287const NIC_TX_MAX_PAYLOAD: usize = 1536;
288
289pub(crate) use udp_queue::UdpRxEntry;
293
294static mut UDP_RX_QUEUE: [UdpRxEntry; UDP_RX_QUEUE_SIZE] = [UdpRxEntry::empty(); UDP_RX_QUEUE_SIZE];
295static mut UDP_RX_TICK: u32 = 1;
296
297#[derive(Copy, Clone)]
298struct NicTxEntry {
299 valid: bool,
300 len: usize,
301 offset: usize,
302 payload: [u8; NIC_TX_MAX_PAYLOAD],
303 age: u32,
304}
305
306impl NicTxEntry {
307 const fn empty() -> Self {
308 Self {
309 valid: false,
310 len: 0,
311 offset: 0,
312 payload: [0; NIC_TX_MAX_PAYLOAD],
313 age: 0,
314 }
315 }
316}
317
318static mut NIC_TX_QUEUE: [NicTxEntry; NIC_TX_QUEUE_SIZE] = [NicTxEntry::empty(); NIC_TX_QUEUE_SIZE];
319static mut NIC_TX_DROPS: u32 = 0;
322static mut NIC_TX_TICK: u32 = 1;
323
324use tcp_state::TcpState;
327
328#[derive(Clone)]
329struct TcpSocket {
330 valid: bool,
331 local_port: u16,
332 remote_ip: [u8; 4],
333 remote_port: u16,
334 state: TcpState,
335 snd_nxt: u32,
336 rcv_nxt: u32,
337 rx_buffer: Vec<u8>,
338 fin_received: bool,
339}
340
341impl TcpSocket {
342 const fn empty() -> Self {
343 Self {
344 valid: false,
345 local_port: 0,
346 remote_ip: [0; 4],
347 remote_port: 0,
348 state: TcpState::Closed,
349 snd_nxt: 0,
350 rcv_nxt: 0,
351 rx_buffer: Vec::new(),
352 fin_received: false,
353 }
354 }
355}
356
357static mut TCP_SOCKETS: [TcpSocket; TCP_SOCKET_TABLE_SIZE] = [
358 TcpSocket::empty(),
359 TcpSocket::empty(),
360 TcpSocket::empty(),
361 TcpSocket::empty(),
362 TcpSocket::empty(),
363 TcpSocket::empty(),
364 TcpSocket::empty(),
365 TcpSocket::empty(),
366 TcpSocket::empty(),
367 TcpSocket::empty(),
368 TcpSocket::empty(),
369 TcpSocket::empty(),
370 TcpSocket::empty(),
371 TcpSocket::empty(),
372 TcpSocket::empty(),
373 TcpSocket::empty(),
374 TcpSocket::empty(),
375 TcpSocket::empty(),
376 TcpSocket::empty(),
377 TcpSocket::empty(),
378 TcpSocket::empty(),
379 TcpSocket::empty(),
380 TcpSocket::empty(),
381 TcpSocket::empty(),
382 TcpSocket::empty(),
383 TcpSocket::empty(),
384 TcpSocket::empty(),
385 TcpSocket::empty(),
386 TcpSocket::empty(),
387 TcpSocket::empty(),
388 TcpSocket::empty(),
389 TcpSocket::empty(),
390];
391static mut TCP_EPHEMERAL_PORT: u16 = 40000;
392
393pub struct UdpDatagram {
394 pub src_ip: [u8; 4],
395 pub src_port: u16,
396 pub dst_port: u16,
397 pub payload: Vec<u8>,
398}
399
400pub struct HttpGetResult {
401 pub remote_ip: [u8; 4],
402 pub local_port: u16,
403 pub status_code: u16,
404 pub reason: String,
405 pub headers: alloc::collections::BTreeMap<alloc::string::String, alloc::string::String>,
406 pub body: String,
407}
408
409pub struct HttpsGetResult {
410 pub remote_ip: [u8; 4],
411 pub local_port: u16,
412 pub tls_version: String,
413 pub cipher_suite: String,
414 pub cert_subject: String,
415 pub cert_issuer: String,
416 pub cert_not_after: String,
417 pub status_code: u16,
418 pub reason: String,
419 pub headers: alloc::collections::BTreeMap<alloc::string::String, alloc::string::String>,
420 pub body: String,
421 pub body_bytes: alloc::vec::Vec<u8>,
424}
425
426pub struct DhcpLease {
427 pub ip: [u8; 4],
428 pub gateway: [u8; 4],
429 pub dns: [u8; 4],
430 pub dns2: [u8; 4],
431 pub server: [u8; 4],
432 pub subnet: [u8; 4],
433 pub lease_secs: u32,
434 pub t1_secs: u32,
435 pub t2_secs: u32,
436}
437
438struct DhcpAckInfo {
439 msg_type: u8,
440 yiaddr: [u8; 4],
441 server: [u8; 4],
442 router: [u8; 4],
443 dns: [u8; 4],
444 dns2: [u8; 4],
445 subnet: [u8; 4],
446 lease_secs: u32,
447 t1_secs: u32,
448 t2_secs: u32,
449}
450
451static mut NET_STATE: NetworkState = NetworkState {
452 link_up: false,
453 nic_backend: NicBackend::SoftwareLoopback,
454 mac: [0x02, 0x00, 0xA1, 0xCE, 0x00, 0x01],
455 ip: [0, 0, 0, 0],
466 gateway: [0, 0, 0, 0],
467 dns: [0, 0, 0, 0],
468 dns2: [0, 0, 0, 0],
469 usb_eth_present: false,
470 usb_eth_addr: 0,
471 usb_eth_bulk_in: 0,
472 usb_eth_bulk_out: 0,
473 usb_eth_mps: 0,
474 usb_eth_is_rndis: false,
475 tx_packets: 0,
476 rx_packets: 0,
477 raw_rx_frames: 0,
478 dhcp_active: false,
479 dhcp_server: [0; 4],
480 dhcp_lease_secs: 0,
481 dhcp_subnet: [0; 4],
482 dhcp_t1_secs: 0,
483 dhcp_t2_secs: 0,
484};
485
486static mut DHCP_XID: u32 = 0x41C3_0001;
487static mut DHCP_SERVICE_TICK: u64 = 0;
488static mut DHCP_LEASE_START_TICK: u64 = 0;
489static mut DHCP_RENEW_STAGE: u8 = 0;
490static mut DHCP_NEXT_RENEW_TICK: u64 = 0;
491static mut DHCP_AUTO_PENDING: bool = true;
492static mut DHCP_LAST_AUTO_ATTEMPT_TICK: u64 = 0;
493static mut DHCP_ASYNC_STATE: u8 = 0; static mut DHCP_ASYNC_XID: u32 = 0;
495static mut DHCP_ASYNC_ATTEMPT: usize = 0;
496static mut DHCP_ASYNC_DEADLINE_TICK: u64 = 0;
497static mut DHCP_ASYNC_RENEWING: bool = false;
498static mut DHCP_ASYNC_REBINDING: bool = false;
499static mut LAST_PING_REPLY: Option<PingReply> = None;
500
501pub fn init() {
504 let _lock = NET_LOCK.lock();
505 unsafe {
506 LAST_PING_REPLY = None;
507 NET_STATE.link_up = false;
508 NET_STATE.nic_backend = NicBackend::SoftwareLoopback;
509 ARP_CACHE = [ArpEntry::empty(); ARP_CACHE_SIZE];
510 ARP_TICK = 1;
511 PING_SEQ = 1;
512 DNS_TXID.store(0x1200, core::sync::atomic::Ordering::Relaxed);
513 DNS_SRC_PORT.store(DNS_SRC_PORT_BASE, core::sync::atomic::Ordering::Relaxed);
514 DNS_CACHE = [DnsCacheEntry::empty(); DNS_CACHE_SIZE];
515 UDP_RX_QUEUE = [UdpRxEntry::empty(); UDP_RX_QUEUE_SIZE];
516 UDP_RX_TICK = 1;
517 NIC_TX_QUEUE = [NicTxEntry::empty(); NIC_TX_QUEUE_SIZE];
518 NIC_TX_TICK = 1;
519 TCP_SOCKETS = [
520 TcpSocket::empty(),
521 TcpSocket::empty(),
522 TcpSocket::empty(),
523 TcpSocket::empty(),
524 TcpSocket::empty(),
525 TcpSocket::empty(),
526 TcpSocket::empty(),
527 TcpSocket::empty(),
528 TcpSocket::empty(),
529 TcpSocket::empty(),
530 TcpSocket::empty(),
531 TcpSocket::empty(),
532 TcpSocket::empty(),
533 TcpSocket::empty(),
534 TcpSocket::empty(),
535 TcpSocket::empty(),
536 TcpSocket::empty(),
537 TcpSocket::empty(),
538 TcpSocket::empty(),
539 TcpSocket::empty(),
540 TcpSocket::empty(),
541 TcpSocket::empty(),
542 TcpSocket::empty(),
543 TcpSocket::empty(),
544 TcpSocket::empty(),
545 TcpSocket::empty(),
546 TcpSocket::empty(),
547 TcpSocket::empty(),
548 TcpSocket::empty(),
549 TcpSocket::empty(),
550 TcpSocket::empty(),
551 TcpSocket::empty(),
552 ];
553 TCP_EPHEMERAL_PORT = 40000;
554 DHCP_XID = 0x41C3_0001;
555 DHCP_SERVICE_TICK = 0;
556 DHCP_LEASE_START_TICK = 0;
557 DHCP_RENEW_STAGE = 0;
558 DHCP_AUTO_PENDING = true;
559 DHCP_LAST_AUTO_ATTEMPT_TICK = 0;
560 DHCP_ASYNC_STATE = 0;
561 DHCP_ASYNC_XID = 0;
562 DHCP_ASYNC_ATTEMPT = 0;
563 DHCP_ASYNC_DEADLINE_TICK = 0;
564 DHCP_ASYNC_RENEWING = false;
565 NET_STATE.dhcp_active = false;
566 NET_STATE.dhcp_server = [0; 4];
567 NET_STATE.dhcp_lease_secs = 0;
568 NET_STATE.dhcp_subnet = [0; 4];
569 NET_STATE.dhcp_t1_secs = 0;
570 NET_STATE.dhcp_t2_secs = 0;
571 }
572
573 if self_test() {
574 crate::debug!("[NET] core initialized (IPv4/UDP/TCP/DNS/ARP/ICMP/HTTP/HTTPS parser ready)");
575 } else {
576 crate::debug!("[NET] core initialized with warnings (self-test failed)");
577 }
578}
579
580pub fn status() -> &'static mut NetworkState {
581 unsafe { &mut NET_STATE }
582}
583
584pub fn status_line() -> String {
585 let _lock = NET_LOCK.lock();
586 let st = status();
587
588 let nic = if st.usb_eth_present {
589 alloc::format!(
590 "usb-eth(addr={},in={},out={},mps={},type={})",
591 st.usb_eth_addr,
592 st.usb_eth_bulk_in,
593 st.usb_eth_bulk_out,
594 st.usb_eth_mps,
595 if st.usb_eth_is_rndis {
596 "rndis"
597 } else {
598 "cdc-ecm"
599 }
600 )
601 } else {
602 String::from("none")
603 };
604
605 let mut dns_list = alloc::vec::Vec::new();
606 if st.dns != [0, 0, 0, 0] {
607 dns_list.push(format_ipv4(st.dns));
608 }
609 if st.dns2 != [0, 0, 0, 0] && st.dns2 != st.dns {
610 dns_list.push(format_ipv4(st.dns2));
611 }
612 for fallback in &[[8, 8, 8, 8], [1, 1, 1, 1]] {
613 if st.dns != *fallback && st.dns2 != *fallback {
614 dns_list.push(format_ipv4(*fallback));
615 }
616 }
617 let dns_display = if dns_list.is_empty() {
618 String::from("none")
619 } else {
620 dns_list.join(", ")
621 };
622
623 alloc::format!(
624 "link={}\nbackend={}\nnic={}\nip={}\nmask={}\ngw={}\ndns={}\ntx={}\nrx={}",
625 if st.link_up { "up" } else { "down" },
626 nic_backend_name(st.nic_backend),
627 nic,
628 format_ipv4(st.ip),
629 format_ipv4(st.dhcp_subnet),
630 format_ipv4(st.gateway),
631 dns_display,
632 st.tx_packets,
633 st.rx_packets
634 )
635}
636
637pub fn nic_debug_info() -> String {
638 let _lock = NET_LOCK.lock();
639 let st = status();
640 let async_state = unsafe { DHCP_ASYNC_STATE };
641 let async_attempt = unsafe { DHCP_ASYNC_ATTEMPT };
642 let async_xid = unsafe { DHCP_ASYNC_XID };
643 let tick = unsafe { DHCP_SERVICE_TICK };
644
645 let mut tx_q_count = 0;
646 unsafe {
647 for e in &NIC_TX_QUEUE {
648 if e.valid {
649 tx_q_count += 1;
650 }
651 }
652 }
653
654 let state_str = match async_state {
655 0 => {
656 if st.dhcp_active {
657 "Bound"
658 } else {
659 "Idle"
660 }
661 }
662 1 => "WaitOffer",
663 2 => "WaitAck",
664 _ => "Unknown",
665 };
666
667 alloc::format!(
668 "NIC DEBUG INFO:\n\
669 Backend: {} (MAC: {})\n\
670 USB Eth: present={} type={} addr={} in={} out={} mps={}\n\
671 Packets: TX={} RX={} RAW_RX_FRAMES={}\n\
672 TX Queue: {}/{} used\n\
673 DHCP State: active={} auto_pending={}\n\
674 DHCP Async: state={} ({}) attempt={} xid={:#010X} tick={}",
675 nic_backend_name(st.nic_backend),
676 format_mac(st.mac),
677 st.usb_eth_present,
678 if st.usb_eth_is_rndis {
679 "rndis"
680 } else {
681 "cdc-ecm"
682 },
683 st.usb_eth_addr,
684 st.usb_eth_bulk_in,
685 st.usb_eth_bulk_out,
686 st.usb_eth_mps,
687 st.tx_packets,
688 st.rx_packets,
689 st.raw_rx_frames,
690 tx_q_count,
691 NIC_TX_QUEUE_SIZE,
692 st.dhcp_active,
693 unsafe { DHCP_AUTO_PENDING },
694 async_state,
695 state_str,
696 async_attempt,
697 async_xid,
698 tick
699 )
700}
701
702pub fn register_usb_ethernet(
703 address: u8,
704 bulk_in_ep: u8,
705 bulk_out_ep: u8,
706 max_packet: u16,
707 is_rndis: bool,
708 mac: [u8; 6],
709) {
710 let _lock = NET_LOCK.lock();
711 unsafe {
712 NET_STATE.usb_eth_present = true;
713 NET_STATE.nic_backend = NicBackend::UsbEthernet;
714 NET_STATE.usb_eth_addr = address;
715 NET_STATE.usb_eth_bulk_in = bulk_in_ep;
716 NET_STATE.usb_eth_bulk_out = bulk_out_ep;
717 NET_STATE.usb_eth_mps = max_packet;
718 NET_STATE.usb_eth_is_rndis = is_rndis;
719 NET_STATE.mac = mac;
720 NET_STATE.link_up = true;
721 DHCP_AUTO_PENDING = true;
722 DHCP_LAST_AUTO_ATTEMPT_TICK = 0;
723 }
724
725 crate::info!(
726 "NET: USB Ethernet registered at addr {} (in ep {}, out ep {}, mps {}, type={})",
727 address,
728 bulk_in_ep,
729 bulk_out_ep,
730 max_packet,
731 if is_rndis { "RNDIS" } else { "CDC-ECM" }
732 );
733}
734
735pub fn nic_backend_name(backend: NicBackend) -> &'static str {
736 match backend {
737 NicBackend::SoftwareLoopback => "software-loopback",
738 NicBackend::UsbEthernet => "usb-ethernet",
739 }
740}
741
742fn self_test() -> bool {
743 let mut query = [0u8; 128];
744 let qlen = match dns::build_dns_query("example.com", 0xCAFE, &mut query) {
745 Ok(n) => n,
746 Err(_) => return false,
747 };
748
749 if qlen < 20 {
750 return false;
751 }
752
753 let response: [u8; 45] = [
754 0xCA, 0xFE, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, b'e', b'x',
755 b'a', b'm', b'p', b'l', b'e', 0x03, b'c', b'o', b'm', 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0,
756 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x04, 192, 168, 1, 100,
757 ];
758
759 if !matches!(
760 dns::parse_dns_a_response(&response, 0xCAFE, "example.com"),
761 Ok(Some([192, 168, 1, 100]))
762 ) {
763 return false;
764 }
765
766
767
768 let req = match http::build_http_get_request_pub("example.com", "/") {
769 Ok(v) => v,
770 Err(_) => return false,
771 };
772 if !req.starts_with("GET /") {
773 return false;
774 }
775
776 let sample = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nhey";
777 let parsed = match http::parse_http_response(sample) {
778 Ok(v) => v,
779 Err(_) => return false,
780 };
781 if parsed.0 != 200 || parsed.3 != "hey" {
782 return false;
783 }
784
785
786
787 let mut dhcp_discover = [0u8; 320];
788 let discover_len = match dhcp::build_dhcp_discover_payload_pub(
789 0x1122_3344,
790 [2, 0, 0xA1, 0xCE, 0, 1],
791 68,
792 67,
793 &mut dhcp_discover,
794 ) {
795 Ok(v) => v,
796 Err(_) => return false,
797 };
798 if discover_len < 244 {
799 return false;
800 }
801
802 let mut dhcp_ack = [0u8; 320];
803 let ack_len = match dhcp::build_dhcp_ack_payload_pub(
804 0x1122_3344,
805 [2, 0, 0xA1, 0xCE, 0, 1],
806 [192, 168, 2, 200],
807 [192, 168, 2, 1],
808 [192, 168, 2, 1],
809 3600,
810 &mut dhcp_ack,
811 ) {
812 Ok(v) => v,
813 Err(_) => return false,
814 };
815 let parsed_ack = match dhcp::parse_dhcp_ack_payload_pub(&dhcp_ack[..ack_len], 0x1122_3344) {
816 Ok(v) => v,
817 Err(_) => return false,
818 };
819 if parsed_ack.msg_type != 5 {
820 return false;
821 }
822 if parsed_ack.yiaddr != [192, 168, 2, 200] {
823 return false;
824 }
825 if parsed_ack.subnet != [255, 255, 255, 0] {
826 return false;
827 }
828 if parsed_ack.t1_secs == 0 || parsed_ack.t2_secs == 0 {
829 return false;
830 }
831
832 true
833}