1//! PXE Base Code protocol.
2
3use core::{
4 ffi::c_void,
5 iter::from_fn,
6 marker::{PhantomData, PhantomPinned},
7 mem::MaybeUninit,
8 ptr::{null, null_mut},
9};
10
11use crate::util::ptr_write_unaligned_and_add;
12use crate::{polyfill::maybe_uninit_slice_as_mut_ptr, proto::unsafe_protocol};
13use bitflags::bitflags;
14use ptr_meta::Pointee;
15
16use crate::{CStr8, Char8, Result, Status};
17
18use super::{IpAddress, MacAddress};
19
20/// PXE Base Code protocol
21#[repr(C)]
22#[unsafe_protocol("03c4e603-ac28-11d3-9a2d-0090273fc14d")]
23#[allow(clippy::type_complexity)]
24pub struct BaseCode {
25 revision: u64,
26 start: extern "efiapi" fn(this: &Self, use_ipv6: bool) -> Status,
27 stop: extern "efiapi" fn(this: &Self) -> Status,
28 dhcp: extern "efiapi" fn(this: &Self, sort_offers: bool) -> Status,
29 discover: extern "efiapi" fn(
30 this: &Self,
31 ty: BootstrapType,
32 layer: &mut u16,
33 use_bis: bool,
34 info: *const FfiDiscoverInfo,
35 ) -> Status,
36 mtftp: unsafe extern "efiapi" fn(
37 this: &Self,
38 operation: TftpOpcode,
39 buffer: *mut c_void,
40 overwrite: bool,
41 buffer_size: &mut u64,
42 block_size: Option<&usize>,
43 server_ip: &IpAddress,
44 filename: *const Char8,
45 info: Option<&MtftpInfo>,
46 dont_use_buffer: bool,
47 ) -> Status,
48 udp_write: unsafe extern "efiapi" fn(
49 this: &Self,
50 op_flags: UdpOpFlags,
51 dest_ip: &IpAddress,
52 dest_port: &u16,
53 gateway_ip: Option<&IpAddress>,
54 src_ip: Option<&IpAddress>,
55 src_port: Option<&mut u16>,
56 header_size: Option<&usize>,
57 header_ptr: *const c_void,
58 buffer_size: &usize,
59 buffer_ptr: *const c_void,
60 ) -> Status,
61 udp_read: unsafe extern "efiapi" fn(
62 this: &Self,
63 op_flags: UdpOpFlags,
64 dest_ip: Option<&mut IpAddress>,
65 dest_port: Option<&mut u16>,
66 src_ip: Option<&mut IpAddress>,
67 src_port: Option<&mut u16>,
68 header_size: Option<&usize>,
69 header_ptr: *mut c_void,
70 buffer_size: &mut usize,
71 buffer_ptr: *mut c_void,
72 ) -> Status,
73 set_ip_filter: extern "efiapi" fn(this: &Self, new_filter: &IpFilter) -> Status,
74 arp: extern "efiapi" fn(
75 this: &Self,
76 ip_addr: &IpAddress,
77 mac_addr: Option<&mut MacAddress>,
78 ) -> Status,
79 set_parameters: extern "efiapi" fn(
80 this: &Self,
81 new_auto_arp: Option<&bool>,
82 new_send_guid: Option<&bool>,
83 new_ttl: Option<&u8>,
84 new_tos: Option<&u8>,
85 new_make_callback: Option<&bool>,
86 ) -> Status,
87 set_station_ip: extern "efiapi" fn(
88 this: &Self,
89 new_station_ip: Option<&IpAddress>,
90 new_subnet_mask: Option<&IpAddress>,
91 ) -> Status,
92 set_packets: extern "efiapi" fn(
93 this: &Self,
94 new_dhcp_discover_valid: Option<&bool>,
95 new_dhcp_ack_received: Option<&bool>,
96 new_proxy_offer_received: Option<&bool>,
97 new_pxe_discover_valid: Option<&bool>,
98 new_pxe_reply_received: Option<&bool>,
99 new_pxe_bis_reply_received: Option<&bool>,
100 new_dhcp_discover: Option<&Packet>,
101 new_dhcp_ack: Option<&Packet>,
102 new_proxy_offer: Option<&Packet>,
103 new_pxe_discover: Option<&Packet>,
104 new_pxe_reply: Option<&Packet>,
105 new_pxe_bis_reply: Option<&Packet>,
106 ) -> Status,
107 mode: *const Mode,
108}
109
110impl BaseCode {
111 /// Enables the use of the PXE Base Code Protocol functions.
112 pub fn start(&mut self, use_ipv6: bool) -> Result {
113 (self.start)(self, use_ipv6).into()
114 }
115
116 /// Disables the use of the PXE Base Code Protocol functions.
117 pub fn stop(&mut self) -> Result {
118 (self.stop)(self).into()
119 }
120
121 /// Attempts to complete a DHCPv4 D.O.R.A. (discover / offer / request /
122 /// acknowledge) or DHCPv6 S.A.R.R (solicit / advertise / request / reply) sequence.
123 pub fn dhcp(&mut self, sort_offers: bool) -> Result {
124 (self.dhcp)(self, sort_offers).into()
125 }
126
127 /// Attempts to complete the PXE Boot Server and/or boot image discovery
128 /// sequence.
129 pub fn discover(
130 &mut self,
131 ty: BootstrapType,
132 layer: &mut u16,
133 use_bis: bool,
134 info: Option<&DiscoverInfo>,
135 ) -> Result {
136 let info: *const FfiDiscoverInfo = info
137 .map(|info| {
138 let info_ptr: *const DiscoverInfo = info;
139 info_ptr.cast()
140 })
141 .unwrap_or(null());
142
143 (self.discover)(self, ty, layer, use_bis, info).into()
144 }
145
146 /// Returns the size of a file located on a TFTP server.
147 pub fn tftp_get_file_size(&mut self, server_ip: &IpAddress, filename: &CStr8) -> Result<u64> {
148 let mut buffer_size = 0;
149
150 let status = unsafe {
151 (self.mtftp)(
152 self,
153 TftpOpcode::TftpGetFileSize,
154 null_mut(),
155 false,
156 &mut buffer_size,
157 None,
158 server_ip,
159 filename.as_ptr(),
160 None,
161 false,
162 )
163 };
164 Result::from(status)?;
165
166 Ok(buffer_size)
167 }
168
169 /// Reads a file located on a TFTP server.
170 pub fn tftp_read_file(
171 &mut self,
172 server_ip: &IpAddress,
173 filename: &CStr8,
174 buffer: Option<&mut [u8]>,
175 ) -> Result<u64> {
176 let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer {
177 let buffer_size = u64::try_from(buffer.len()).unwrap();
178 ((&mut buffer[0] as *mut u8).cast(), buffer_size, false)
179 } else {
180 (null_mut(), 0, true)
181 };
182
183 let status = unsafe {
184 (self.mtftp)(
185 self,
186 TftpOpcode::TftpReadFile,
187 buffer_ptr,
188 false,
189 &mut buffer_size,
190 None,
191 server_ip,
192 filename.as_ptr(),
193 None,
194 dont_use_buffer,
195 )
196 };
197 Result::from(status)?;
198
199 Ok(buffer_size)
200 }
201
202 /// Writes to a file located on a TFTP server.
203 pub fn tftp_write_file(
204 &mut self,
205 server_ip: &IpAddress,
206 filename: &CStr8,
207 overwrite: bool,
208 buffer: &[u8],
209 ) -> Result {
210 let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast();
211 let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64");
212
213 unsafe {
214 (self.mtftp)(
215 self,
216 TftpOpcode::TftpWriteFile,
217 buffer_ptr,
218 overwrite,
219 &mut buffer_size,
220 None,
221 server_ip,
222 filename.as_ptr(),
223 None,
224 false,
225 )
226 }
227 .into()
228 }
229
230 /// Reads a directory listing of a directory on a TFTP server.
231 pub fn tftp_read_dir<'a>(
232 &self,
233 server_ip: &IpAddress,
234 directory_name: &CStr8,
235 buffer: &'a mut [u8],
236 ) -> Result<impl Iterator<Item = core::result::Result<TftpFileInfo<'a>, ReadDirParseError>> + 'a>
237 {
238 let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast();
239 let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64");
240
241 let status = unsafe {
242 (self.mtftp)(
243 self,
244 TftpOpcode::TftpReadDirectory,
245 buffer_ptr,
246 false,
247 &mut buffer_size,
248 None,
249 server_ip,
250 directory_name.as_ptr(),
251 None,
252 false,
253 )
254 };
255 Result::from(status)?;
256
257 let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize");
258 let buffer = &buffer[..buffer_size];
259
260 let mut iterator = buffer.split_inclusive(|b| *b == 0);
261 let mut parse_next = move || {
262 let filename = iterator.next().ok_or(ReadDirParseError)?;
263 if filename == [0] {
264 // This is the final entry.
265 return Ok(None);
266 }
267 let filename = CStr8::from_bytes_with_nul(filename).unwrap();
268
269 let information_string = iterator.next().ok_or(ReadDirParseError)?;
270 let (_null_terminator, information_string) = information_string.split_last().unwrap();
271 let information_string =
272 core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?;
273
274 let (size, rest) = information_string
275 .split_once(' ')
276 .ok_or(ReadDirParseError)?;
277 let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?;
278 let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?;
279 let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?;
280 let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?;
281 let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?;
282
283 let size = size.parse().map_err(|_| ReadDirParseError)?;
284 let year = year.parse().map_err(|_| ReadDirParseError)?;
285 let month = month.parse().map_err(|_| ReadDirParseError)?;
286 let day = day.parse().map_err(|_| ReadDirParseError)?;
287 let hour = hour.parse().map_err(|_| ReadDirParseError)?;
288 let minute = minute.parse().map_err(|_| ReadDirParseError)?;
289 let second = second.parse().map_err(|_| ReadDirParseError)?;
290
291 Ok(Some(TftpFileInfo {
292 filename,
293 size,
294 year,
295 month,
296 day,
297 hour,
298 minute,
299 second,
300 }))
301 };
302 Ok(from_fn(move || parse_next().transpose()).fuse())
303 }
304
305 /// Returns the size of a file located on a MTFTP server.
306 pub fn mtftp_get_file_size(
307 &mut self,
308 server_ip: &IpAddress,
309 filename: &CStr8,
310 info: &MtftpInfo,
311 ) -> Result<u64> {
312 let mut buffer_size = 0;
313
314 let status = unsafe {
315 (self.mtftp)(
316 self,
317 TftpOpcode::MtftpGetFileSize,
318 null_mut(),
319 false,
320 &mut buffer_size,
321 None,
322 server_ip,
323 filename.as_ptr(),
324 Some(info),
325 false,
326 )
327 };
328 Result::from(status)?;
329
330 Ok(buffer_size)
331 }
332
333 /// Reads a file located on a MTFTP server.
334 pub fn mtftp_read_file(
335 &mut self,
336 server_ip: &IpAddress,
337 filename: &CStr8,
338 buffer: Option<&mut [u8]>,
339 info: &MtftpInfo,
340 ) -> Result<u64> {
341 let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer {
342 let buffer_size = u64::try_from(buffer.len()).unwrap();
343 ((&mut buffer[0] as *mut u8).cast(), buffer_size, false)
344 } else {
345 (null_mut(), 0, true)
346 };
347
348 let status = unsafe {
349 (self.mtftp)(
350 self,
351 TftpOpcode::MtftpReadFile,
352 buffer_ptr,
353 false,
354 &mut buffer_size,
355 None,
356 server_ip,
357 filename.as_ptr(),
358 Some(info),
359 dont_use_buffer,
360 )
361 };
362 Result::from(status)?;
363
364 Ok(buffer_size)
365 }
366
367 /// Reads a directory listing of a directory on a MTFTP server.
368 pub fn mtftp_read_dir<'a>(
369 &self,
370 server_ip: &IpAddress,
371 buffer: &'a mut [u8],
372 info: &MtftpInfo,
373 ) -> Result<impl Iterator<Item = core::result::Result<MtftpFileInfo<'a>, ReadDirParseError>> + 'a>
374 {
375 let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast();
376 let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64");
377
378 let status = unsafe {
379 (self.mtftp)(
380 self,
381 TftpOpcode::MtftpReadDirectory,
382 buffer_ptr,
383 false,
384 &mut buffer_size,
385 None,
386 server_ip,
387 null_mut(),
388 Some(info),
389 false,
390 )
391 };
392 Result::from(status)?;
393
394 let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize");
395 let buffer = &buffer[..buffer_size];
396
397 let mut iterator = buffer.split_inclusive(|b| *b == 0);
398 let mut parse_next = move || {
399 let filename = iterator.next().ok_or(ReadDirParseError)?;
400 if filename == [0] {
401 // This is the final entry.
402 return Ok(None);
403 }
404 let filename = CStr8::from_bytes_with_nul(filename).unwrap();
405
406 let multicast_ip = iterator.next().ok_or(ReadDirParseError)?;
407 let (_null_terminator, multicast_ip) = multicast_ip.split_last().unwrap();
408 let multicast_ip = core::str::from_utf8(multicast_ip).map_err(|_| ReadDirParseError)?;
409 let mut octets = multicast_ip.split('.');
410 let mut buffer = [0; 4];
411 for b in buffer.iter_mut() {
412 let octet = octets.next().ok_or(ReadDirParseError)?;
413 let octet = octet.parse().map_err(|_| ReadDirParseError)?;
414 *b = octet;
415 }
416 if octets.next().is_some() {
417 // The IP should have exact 4 octets, not more.
418 return Err(ReadDirParseError);
419 }
420 let ip_address = IpAddress::new_v4(buffer);
421
422 let information_string = iterator.next().ok_or(ReadDirParseError)?;
423 let (_null_terminator, information_string) = information_string.split_last().unwrap();
424 let information_string =
425 core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?;
426
427 let (size, rest) = information_string
428 .split_once(' ')
429 .ok_or(ReadDirParseError)?;
430 let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?;
431 let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?;
432 let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?;
433 let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?;
434 let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?;
435
436 let size = size.parse().map_err(|_| ReadDirParseError)?;
437 let year = year.parse().map_err(|_| ReadDirParseError)?;
438 let month = month.parse().map_err(|_| ReadDirParseError)?;
439 let day = day.parse().map_err(|_| ReadDirParseError)?;
440 let hour = hour.parse().map_err(|_| ReadDirParseError)?;
441 let minute = minute.parse().map_err(|_| ReadDirParseError)?;
442 let second = second.parse().map_err(|_| ReadDirParseError)?;
443
444 Ok(Some(MtftpFileInfo {
445 filename,
446 ip_address,
447 size,
448 year,
449 month,
450 day,
451 hour,
452 minute,
453 second,
454 }))
455 };
456 Ok(from_fn(move || parse_next().transpose()).fuse())
457 }
458
459 /// Writes a UDP packet to the network interface.
460 #[allow(clippy::too_many_arguments)]
461 pub fn udp_write(
462 &mut self,
463 op_flags: UdpOpFlags,
464 dest_ip: &IpAddress,
465 dest_port: u16,
466 gateway_ip: Option<&IpAddress>,
467 src_ip: Option<&IpAddress>,
468 src_port: Option<&mut u16>,
469 header: Option<&[u8]>,
470 buffer: &[u8],
471 ) -> Result {
472 let header_size_tmp;
473 let (header_size, header_ptr) = if let Some(header) = header {
474 header_size_tmp = header.len();
475 (Some(&header_size_tmp), (&header[0] as *const u8).cast())
476 } else {
477 (None, null())
478 };
479
480 unsafe {
481 (self.udp_write)(
482 self,
483 op_flags,
484 dest_ip,
485 &dest_port,
486 gateway_ip,
487 src_ip,
488 src_port,
489 header_size,
490 header_ptr,
491 &buffer.len(),
492 (&buffer[0] as *const u8).cast(),
493 )
494 }
495 .into()
496 }
497
498 /// Reads a UDP packet from the network interface.
499 #[allow(clippy::too_many_arguments)]
500 pub fn udp_read(
501 &mut self,
502 op_flags: UdpOpFlags,
503 dest_ip: Option<&mut IpAddress>,
504 dest_port: Option<&mut u16>,
505 src_ip: Option<&mut IpAddress>,
506 src_port: Option<&mut u16>,
507 header: Option<&mut [u8]>,
508 buffer: &mut [u8],
509 ) -> Result<usize> {
510 let header_size_tmp;
511 let (header_size, header_ptr) = if let Some(header) = header {
512 header_size_tmp = header.len();
513 (Some(&header_size_tmp), (&mut header[0] as *mut u8).cast())
514 } else {
515 (None, null_mut())
516 };
517
518 let mut buffer_size = buffer.len();
519
520 let status = unsafe {
521 (self.udp_read)(
522 self,
523 op_flags,
524 dest_ip,
525 dest_port,
526 src_ip,
527 src_port,
528 header_size,
529 header_ptr,
530 &mut buffer_size,
531 (&mut buffer[0] as *mut u8).cast(),
532 )
533 };
534 Result::from(status)?;
535
536 Ok(buffer_size)
537 }
538
539 /// Updates the IP receive filters of a network device and enables software
540 /// filtering.
541 pub fn set_ip_filter(&mut self, new_filter: &IpFilter) -> Result {
542 (self.set_ip_filter)(self, new_filter).into()
543 }
544
545 /// Uses the ARP protocol to resolve a MAC address.
546 pub fn arp(&mut self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddress>) -> Result {
547 (self.arp)(self, ip_addr, mac_addr).into()
548 }
549
550 /// Updates the parameters that affect the operation of the PXE Base Code
551 /// Protocol.
552 pub fn set_parameters(
553 &mut self,
554 new_auto_arp: Option<bool>,
555 new_send_guid: Option<bool>,
556 new_ttl: Option<u8>,
557 new_tos: Option<u8>,
558 new_make_callback: Option<bool>,
559 ) -> Result {
560 (self.set_parameters)(
561 self,
562 new_auto_arp.as_ref(),
563 new_send_guid.as_ref(),
564 new_ttl.as_ref(),
565 new_tos.as_ref(),
566 new_make_callback.as_ref(),
567 )
568 .into()
569 }
570
571 /// Updates the station IP address and/or subnet mask values of a network
572 /// device.
573 pub fn set_station_ip(
574 &mut self,
575 new_station_ip: Option<&IpAddress>,
576 new_subnet_mask: Option<&IpAddress>,
577 ) -> Result {
578 (self.set_station_ip)(self, new_station_ip, new_subnet_mask).into()
579 }
580
581 /// Updates the contents of the cached DHCP and Discover packets.
582 #[allow(clippy::too_many_arguments)]
583 pub fn set_packets(
584 &mut self,
585 new_dhcp_discover_valid: Option<bool>,
586 new_dhcp_ack_received: Option<bool>,
587 new_proxy_offer_received: Option<bool>,
588 new_pxe_discover_valid: Option<bool>,
589 new_pxe_reply_received: Option<bool>,
590 new_pxe_bis_reply_received: Option<bool>,
591 new_dhcp_discover: Option<&Packet>,
592 new_dhcp_ack: Option<&Packet>,
593 new_proxy_offer: Option<&Packet>,
594 new_pxe_discover: Option<&Packet>,
595 new_pxe_reply: Option<&Packet>,
596 new_pxe_bis_reply: Option<&Packet>,
597 ) -> Result {
598 (self.set_packets)(
599 self,
600 new_dhcp_discover_valid.as_ref(),
601 new_dhcp_ack_received.as_ref(),
602 new_proxy_offer_received.as_ref(),
603 new_pxe_discover_valid.as_ref(),
604 new_pxe_reply_received.as_ref(),
605 new_pxe_bis_reply_received.as_ref(),
606 new_dhcp_discover,
607 new_dhcp_ack,
608 new_proxy_offer,
609 new_pxe_discover,
610 new_pxe_reply,
611 new_pxe_bis_reply,
612 )
613 .into()
614 }
615
616 /// Returns a reference to the `Mode` struct.
617 #[must_use]
618 pub const fn mode(&self) -> &Mode {
619 unsafe { &*self.mode }
620 }
621}
622
623/// A type of bootstrap to perform in [`BaseCode::discover`].
624///
625/// Corresponds to the `EFI_PXE_BASE_CODE_BOOT_` constants in the C API.
626#[derive(Clone, Copy, PartialEq, Eq, Hash)]
627#[repr(u16)]
628#[allow(missing_docs)]
629pub enum BootstrapType {
630 Bootstrap = 0,
631 MsWinntRis = 1,
632 IntelLcm = 2,
633 DosUndi = 3,
634 NecEsmpro = 4,
635 IbmWsoD = 5,
636 IbmLccm = 6,
637 CaUnicenterTng = 7,
638 HpOpenview = 8,
639 Altiris9 = 9,
640 Altiris10 = 10,
641 Altiris11 = 11,
642 // NOT_USED_12 = 12,
643 RedhatInstall = 13,
644 RedhatBoot = 14,
645 Rembo = 15,
646 Beoboot = 16,
647 //
648 // Values 17 through 32767 are reserved.
649 // Values 32768 through 65279 are for vendor use.
650 // Values 65280 through 65534 are reserved.
651 //
652 PxeTest = 65535,
653}
654
655/// Opaque type that should be used to represent a pointer to a [`DiscoverInfo`] in
656/// foreign function interfaces. This type produces a thin pointer, unlike
657/// [`DiscoverInfo`].
658#[repr(C, packed)]
659pub struct FfiDiscoverInfo {
660 // This representation is recommended by the nomicon:
661 // https://doc.rust-lang.org/stable/nomicon/ffi.html#representing-opaque-structs
662 _data: [u8; 0],
663 _marker: PhantomData<(*mut u8, PhantomPinned)>,
664}
665
666/// This struct contains optional parameters for [`BaseCode::discover`].
667///
668/// Corresponds to the `EFI_PXE_BASE_CODE_DISCOVER_INFO` type in the C API.
669#[repr(C)]
670#[derive(Pointee)]
671pub struct DiscoverInfo {
672 use_m_cast: bool,
673 use_b_cast: bool,
674 use_u_cast: bool,
675 must_use_list: bool,
676 server_m_cast_ip: IpAddress,
677 ip_cnt: u16,
678 srv_list: [Server],
679}
680
681impl DiscoverInfo {
682 /// Create a `DiscoverInfo`.
683 pub fn new_in_buffer<'buf>(
684 buffer: &'buf mut [MaybeUninit<u8>],
685 use_m_cast: bool,
686 use_b_cast: bool,
687 use_u_cast: bool,
688 must_use_list: bool,
689 server_m_cast_ip: IpAddress,
690 srv_list: &[Server],
691 ) -> Result<&'buf mut Self> {
692 let server_count = srv_list.len();
693 assert!(server_count <= u16::MAX as usize, "too many servers");
694
695 let required_size = core::mem::size_of::<bool>() * 4
696 + core::mem::size_of::<IpAddress>()
697 + core::mem::size_of::<u16>()
698 + core::mem::size_of::<Server>() * server_count;
699
700 if buffer.len() < required_size {
701 return Err(Status::BUFFER_TOO_SMALL.into());
702 }
703
704 let mut ptr: *mut u8 = maybe_uninit_slice_as_mut_ptr(buffer);
705 unsafe {
706 ptr_write_unaligned_and_add(&mut ptr, use_m_cast);
707 ptr_write_unaligned_and_add(&mut ptr, use_b_cast);
708 ptr_write_unaligned_and_add(&mut ptr, use_u_cast);
709 ptr_write_unaligned_and_add(&mut ptr, must_use_list);
710 ptr_write_unaligned_and_add(&mut ptr, server_m_cast_ip);
711 ptr_write_unaligned_and_add(&mut ptr, server_count as u16);
712
713 ptr = ptr.add(2); // Align server list (4-byte alignment).
714 core::ptr::copy(srv_list.as_ptr(), ptr.cast(), server_count);
715
716 let ptr: *mut Self =
717 ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), server_count);
718 Ok(&mut *ptr)
719 }
720 }
721}
722
723impl DiscoverInfo {
724 /// Returns whether discovery should use multicast.
725 #[must_use]
726 pub const fn use_m_cast(&self) -> bool {
727 self.use_m_cast
728 }
729
730 /// Returns whether discovery should use broadcast.
731 #[must_use]
732 pub const fn use_b_cast(&self) -> bool {
733 self.use_b_cast
734 }
735
736 /// Returns whether discovery should use unicast.
737 #[must_use]
738 pub const fn use_u_cast(&self) -> bool {
739 self.use_u_cast
740 }
741
742 /// Returns whether discovery should only accept boot servers in the server
743 /// list (boot server verification).
744 #[must_use]
745 pub const fn must_use_list(&self) -> bool {
746 self.must_use_list
747 }
748
749 /// Returns the address used in multicast discovery.
750 #[must_use]
751 pub const fn server_m_cast_ip(&self) -> &IpAddress {
752 &self.server_m_cast_ip
753 }
754
755 /// Returns the amount of Boot Server.
756 #[must_use]
757 pub const fn ip_cnt(&self) -> u16 {
758 self.ip_cnt
759 }
760
761 /// Returns the Boot Server list used for unicast discovery or boot server
762 /// verification.
763 #[must_use]
764 pub const fn srv_list(&self) -> &[Server] {
765 &self.srv_list
766 }
767}
768
769/// An entry in the Boot Server list
770///
771/// Corresponds to the `EFI_PXE_BASE_CODE_SRVLIST` type in the C API.
772#[repr(C)]
773pub struct Server {
774 /// The type of Boot Server reply
775 pub ty: u16,
776 accept_any_response: bool,
777 _reserved: u8,
778 /// The IP address of the server
779 ip_addr: IpAddress,
780}
781
782impl Server {
783 /// Construct a `Server` for a Boot Server reply type. If `ip_addr` is not
784 /// `None` only Boot Server replies with matching the IP address will be
785 /// accepted.
786 #[must_use]
787 pub fn new(ty: u16, ip_addr: Option<IpAddress>) -> Self {
788 Self {
789 ty,
790 accept_any_response: ip_addr.is_none(),
791 _reserved: 0,
792 ip_addr: ip_addr.unwrap_or(IpAddress([0; 16])),
793 }
794 }
795
796 /// Returns a `None` if the any response should be accepted or the IP
797 /// address of a Boot Server whose responses should be accepted.
798 #[must_use]
799 pub const fn ip_addr(&self) -> Option<&IpAddress> {
800 if self.accept_any_response {
801 None
802 } else {
803 Some(&self.ip_addr)
804 }
805 }
806}
807
808/// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_OPCODE` type in the C API.
809#[repr(C)]
810enum TftpOpcode {
811 TftpGetFileSize = 1,
812 TftpReadFile,
813 TftpWriteFile,
814 TftpReadDirectory,
815 MtftpGetFileSize,
816 MtftpReadFile,
817 MtftpReadDirectory,
818}
819
820/// MTFTP connection parameters
821///
822/// Corresponds to the `EFI_PXE_BASE_CODE_MTFTP_INFO` type in the C API.
823#[derive(Clone, Copy)]
824#[repr(C)]
825pub struct MtftpInfo {
826 /// File multicast IP address. This is the IP address to which the server
827 /// will send the requested file.
828 pub m_cast_ip: IpAddress,
829 /// Client multicast listening port. This is the UDP port to which the
830 /// server will send the requested file.
831 pub c_port: u16,
832 /// Server multicast listening port. This is the UDP port on which the
833 /// server listens for multicast open requests and data acks.
834 pub s_port: u16,
835 /// The number of seconds a client should listen for an active multicast
836 /// session before requesting a new multicast session.
837 pub listen_timeout: u16,
838 /// The number of seconds a client should wait for a packet from the server
839 /// before retransmitting the previous open request or data ack packet.
840 pub transmit_timeout: u16,
841}
842
843// No corresponding type in the UEFI spec, it just uses UINT16.
844bitflags! {
845 /// Flags for UDP read and write operations.
846 #[repr(transparent)]
847 pub struct UdpOpFlags: u16 {
848 /// Receive a packet sent from any IP address in UDP read operations.
849 const ANY_SRC_IP = 0x0001;
850 /// Receive a packet sent from any UDP port in UDP read operations. If
851 /// the source port is no specified in UDP write operations, the
852 /// source port will be automatically selected.
853 const ANY_SRC_PORT = 0x0002;
854 /// Receive a packet sent to any IP address in UDP read operations.
855 const ANY_DEST_IP = 0x0004;
856 /// Receive a packet sent to any UDP port in UDP read operations.
857 const ANY_DEST_PORT = 0x0008;
858 /// The software filter is used in UDP read operations.
859 const USE_FILTER = 0x0010;
860 /// If required, a UDP write operation may be broken up across multiple packets.
861 const MAY_FRAGMENT = 0x0020;
862 }
863}
864
865/// IP receive filter settings
866///
867/// Corresponds to the `EFI_PXE_BASE_CODE_IP_FILTER` type in the C API.
868#[repr(C)]
869pub struct IpFilter {
870 /// A set of filters.
871 pub filters: IpFilters,
872 ip_cnt: u8,
873 _reserved: u16,
874 ip_list: [IpAddress; 8],
875}
876
877impl IpFilter {
878 /// Construct a new `IpFilter`.
879 ///
880 /// # Panics
881 ///
882 /// Panics if `ip_list` contains more than 8 entries.
883 #[must_use]
884 pub fn new(filters: IpFilters, ip_list: &[IpAddress]) -> Self {
885 assert!(ip_list.len() <= 8);
886
887 let ip_cnt = ip_list.len() as u8;
888 let mut buffer = [IpAddress([0; 16]); 8];
889 buffer[..ip_list.len()].copy_from_slice(ip_list);
890
891 Self {
892 filters,
893 ip_cnt,
894 _reserved: 0,
895 ip_list: buffer,
896 }
897 }
898
899 /// A list of IP addresses other than the Station Ip that should be
900 /// enabled. Maybe be multicast or unicast.
901 #[must_use]
902 pub fn ip_list(&self) -> &[IpAddress] {
903 &self.ip_list[..usize::from(self.ip_cnt)]
904 }
905}
906
907bitflags! {
908 /// IP receive filters.
909 #[repr(transparent)]
910 pub struct IpFilters: u8 {
911 /// Enable the Station IP address.
912 const STATION_IP = 0x01;
913 /// Enable IPv4 broadcast addresses.
914 const BROADCAST = 0x02;
915 /// Enable all addresses.
916 const PROMISCUOUS = 0x04;
917 /// Enable all multicast addresses.
918 const PROMISCUOUS_MULTICAST = 0x08;
919 }
920}
921
922/// A network packet.
923///
924/// Corresponds to the `EFI_PXE_BASE_CODE_PACKET` type in the C API.
925#[repr(C)]
926pub union Packet {
927 raw: [u8; 1472],
928 dhcpv4: DhcpV4Packet,
929 dhcpv6: DhcpV6Packet,
930}
931
932impl AsRef<[u8; 1472]> for Packet {
933 fn as_ref(&self) -> &[u8; 1472] {
934 unsafe { &self.raw }
935 }
936}
937
938impl AsRef<DhcpV4Packet> for Packet {
939 fn as_ref(&self) -> &DhcpV4Packet {
940 unsafe { &self.dhcpv4 }
941 }
942}
943
944impl AsRef<DhcpV6Packet> for Packet {
945 fn as_ref(&self) -> &DhcpV6Packet {
946 unsafe { &self.dhcpv6 }
947 }
948}
949
950/// A Dhcpv4 Packet.
951///
952/// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV4_PACKET` type in the C API.
953#[repr(C)]
954#[derive(Clone, Copy)]
955pub struct DhcpV4Packet {
956 /// Packet op code / message type.
957 pub bootp_opcode: u8,
958 /// Hardware address type.
959 pub bootp_hw_type: u8,
960 /// Hardware address length.
961 pub bootp_hw_addr_len: u8,
962 /// Client sets to zero, optionally used by gateways in cross-gateway booting.
963 pub bootp_gate_hops: u8,
964 bootp_ident: u32,
965 bootp_seconds: u16,
966 bootp_flags: u16,
967 /// Client IP address, filled in by client in bootrequest if known.
968 pub bootp_ci_addr: [u8; 4],
969 /// 'your' (client) IP address; filled by server if client doesn't know its own address (`bootp_ci_addr` was 0).
970 pub bootp_yi_addr: [u8; 4],
971 /// Server IP address, returned in bootreply by server.
972 pub bootp_si_addr: [u8; 4],
973 /// Gateway IP address, used in optional cross-gateway booting.
974 pub bootp_gi_addr: [u8; 4],
975 /// Client hardware address, filled in by client.
976 pub bootp_hw_addr: [u8; 16],
977 /// Optional server host name, null terminated string.
978 pub bootp_srv_name: [u8; 64],
979 /// Boot file name, null terminated string, 'generic' name or null in
980 /// bootrequest, fully qualified directory-path name in bootreply.
981 pub bootp_boot_file: [u8; 128],
982 dhcp_magik: u32,
983 /// Optional vendor-specific area, e.g. could be hardware type/serial on request, or 'capability' / remote file system handle on reply. This info may be set aside for use by a third phase bootstrap or kernel.
984 pub dhcp_options: [u8; 56],
985}
986
987impl DhcpV4Packet {
988 /// The expected value for [`Self::dhcp_magik`].
989 pub const DHCP_MAGIK: u32 = 0x63825363;
990
991 /// Transaction ID, a random number, used to match this boot request with the responses it generates.
992 #[must_use]
993 pub const fn bootp_ident(&self) -> u32 {
994 u32::from_be(self.bootp_ident)
995 }
996
997 /// Filled in by client, seconds elapsed since client started trying to boot.
998 #[must_use]
999 pub const fn bootp_seconds(&self) -> u16 {
1000 u16::from_be(self.bootp_seconds)
1001 }
1002
1003 /// The flags.
1004 #[must_use]
1005 pub const fn bootp_flags(&self) -> DhcpV4Flags {
1006 DhcpV4Flags::from_bits_truncate(u16::from_be(self.bootp_flags))
1007 }
1008
1009 /// A magic cookie, should be [`Self::DHCP_MAGIK`].
1010 #[must_use]
1011 pub const fn dhcp_magik(&self) -> u32 {
1012 u32::from_be(self.dhcp_magik)
1013 }
1014}
1015
1016bitflags! {
1017 /// Represents the 'flags' field for a [`DhcpV4Packet`].
1018 pub struct DhcpV4Flags: u16 {
1019 /// Should be set when the client cannot receive unicast IP datagrams
1020 /// until its protocol software has been configured with an IP address.
1021 const BROADCAST = 1;
1022 }
1023}
1024
1025/// A Dhcpv6 Packet.
1026///
1027/// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV6_PACKET` type in the C API.
1028#[repr(C)]
1029#[derive(Clone, Copy)]
1030pub struct DhcpV6Packet {
1031 /// The message type.
1032 pub message_type: u8,
1033 transaction_id: [u8; 3],
1034 /// A byte array containing dhcp options.
1035 pub dhcp_options: [u8; 1024],
1036}
1037
1038impl DhcpV6Packet {
1039 /// The transaction id.
1040 #[must_use]
1041 pub fn transaction_id(&self) -> u32 {
1042 u32::from(self.transaction_id[0]) << 16
1043 | u32::from(self.transaction_id[1]) << 8
1044 | u32::from(self.transaction_id[2])
1045 }
1046}
1047
1048/// The data values in this structure are read-only and are updated by the
1049/// [`BaseCode`].
1050///
1051/// Corresponds to the `EFI_PXE_BASE_CODE_MODE` type in the C API.
1052#[repr(C)]
1053pub struct Mode {
1054 /// `true` if this device has been started by calling [`BaseCode::start`].
1055 /// This field is set to `true` by [`BaseCode::start`] and to `false` by
1056 /// the [`BaseCode::stop`] function.
1057 pub started: bool,
1058 /// `true` if the UNDI protocol supports IPv6
1059 pub ipv6_available: bool,
1060 /// `true` if this PXE Base Code Protocol implementation supports IPv6.
1061 pub ipv6_supported: bool,
1062 /// `true` if this device is currently using IPv6. This field is set by
1063 /// [`BaseCode::start`].
1064 pub using_ipv6: bool,
1065 /// `true` if this PXE Base Code implementation supports Boot Integrity
1066 /// Services (BIS). This field is set by [`BaseCode::start`].
1067 pub bis_supported: bool,
1068 /// `true` if this device and the platform support Boot Integrity Services
1069 /// (BIS). This field is set by [`BaseCode::start`].
1070 pub bis_detected: bool,
1071 /// `true` for automatic ARP packet generation, `false` otherwise. This
1072 /// field is initialized to `true` by [`BaseCode::start`] and can be
1073 /// modified with [`BaseCode::set_parameters`].
1074 pub auto_arp: bool,
1075 /// This field is used to change the Client Hardware Address (chaddr) field
1076 /// in the DHCP and Discovery packets. Set to `true` to send the SystemGuid
1077 /// (if one is available). Set to `false` to send the client NIC MAC
1078 /// address. This field is initialized to `false` by [`BaseCode::start`]
1079 /// and can be modified with [`BaseCode::set_parameters`].
1080 pub send_guid: bool,
1081 /// This field is initialized to `false` by [`BaseCode::start`] and set to
1082 /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`,
1083 /// [`Self::dhcp_discover`] is valid. This field can also be changed by
1084 /// [`BaseCode::set_packets`].
1085 pub dhcp_discover_valid: bool,
1086 /// This field is initialized to `false` by [`BaseCode::start`] and set to
1087 /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`,
1088 /// [`Self::dhcp_ack`] is valid. This field can also be changed by
1089 /// [`BaseCode::set_packets`].
1090 pub dhcp_ack_received: bool,
1091 /// This field is initialized to `false` by [`BaseCode::start`] and set to
1092 /// `true` when [`BaseCode::dhcp`] completes successfully and a proxy DHCP
1093 /// offer packet was received. When `true`, [`Self::proxy_offer`] is valid.
1094 /// This field can also be changed by [`BaseCode::set_packets`].
1095 pub proxy_offer_received: bool,
1096 /// When `true`, [`Self::pxe_discover`] is valid. This field is set to
1097 /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set
1098 /// to `true` or `false` by [`BaseCode::discover`] and
1099 /// [`BaseCode::set_packets`].
1100 pub pxe_discover_valid: bool,
1101 /// When `true`, [`Self::pxe_reply`] is valid. This field is set to `false`
1102 /// by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set to `true`
1103 /// or `false` by [`BaseCode::discover`] and [`BaseCode::set_packets`].
1104 pub pxe_reply_received: bool,
1105 /// When `true`, [`Self::pxe_bis_reply`] is valid. This field is set to
1106 /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set
1107 /// to `true` or `false` by the [`BaseCode::discover`] and
1108 /// [`BaseCode::set_packets`].
1109 pub pxe_bis_reply_received: bool,
1110 /// Indicates whether [`Self::icmp_error`] has been updated. This field is
1111 /// reset to `false` by [`BaseCode::start`], [`BaseCode::dhcp`],
1112 /// [`BaseCode::discover`],[`BaseCode::udp_read`], [`BaseCode::udp_write`],
1113 /// [`BaseCode::arp`] and any of the TFTP/MTFTP operations. If an ICMP
1114 /// error is received, this field will be set to `true` after
1115 /// [`Self::icmp_error`] is updated.
1116 pub icmp_error_received: bool,
1117 /// Indicates whether [`Self::tftp_error`] has been updated. This field is
1118 /// reset to `false` by [`BaseCode::start`] and any of the TFTP/MTFTP
1119 /// operations. If a TFTP error is received, this field will be set to
1120 /// `true` after [`Self::tftp_error`] is updated.
1121 pub tftp_error_received: bool,
1122 /// When `false`, callbacks will not be made. When `true`, make callbacks
1123 /// to the PXE Base Code Callback Protocol. This field is reset to `false`
1124 /// by [`BaseCode::start`] if the PXE Base Code Callback Protocol is not
1125 /// available. It is reset to `true` by [`BaseCode::start`] if the PXE Base
1126 /// Code Callback Protocol is available.
1127 pub make_callbacks: bool,
1128 /// The "time to live" field of the IP header. This field is initialized to
1129 /// `16` by [`BaseCode::start`] and can be modified by
1130 /// [`BaseCode::set_parameters`].
1131 pub ttl: u8,
1132 /// The type of service field of the IP header. This field is initialized
1133 /// to `0` by [`BaseCode::start`], and can be modified with
1134 /// [`BaseCode::set_parameters`].
1135 pub tos: u8,
1136 /// The device’s current IP address. This field is initialized to a zero
1137 /// address by Start(). This field is set when [`BaseCode::dhcp`] completes
1138 /// successfully. This field can also be set by
1139 /// [`BaseCode::set_station_ip`]. This field must be set to a valid IP
1140 /// address by either [`BaseCode::dhcp`] or [`BaseCode::set_station_ip`]
1141 /// before [`BaseCode::discover`], [`BaseCode::udp_read`],
1142 /// [`BaseCode::udp_write`], [`BaseCode::arp`] and any of the TFTP/MTFTP
1143 /// operations are called.
1144 pub station_ip: IpAddress,
1145 /// The device's current subnet mask. This field is initialized to a zero
1146 /// address by [`BaseCode::start`]. This field is set when
1147 /// [`BaseCode::dhcp`] completes successfully. This field can also be set
1148 /// by [`BaseCode::set_station_ip`]. This field must be set to a valid
1149 /// subnet mask by either [`BaseCode::dhcp`] or
1150 /// [`BaseCode::set_station_ip`] before [`BaseCode::discover`],
1151 /// [`BaseCode::udp_read`], [`BaseCode::udp_write`],
1152 /// [`BaseCode::arp`] or any of the TFTP/MTFTP operations are called.
1153 pub subnet_mask: IpAddress,
1154 /// Cached DHCP Discover packet. This field is zero-filled by the
1155 /// [`BaseCode::start`] function, and is set when [`BaseCode::dhcp`]
1156 /// completes successfully. The contents of this field can replaced by
1157 /// [`BaseCode::set_packets`].
1158 pub dhcp_discover: Packet,
1159 /// Cached DHCP Ack packet. This field is zero-filled by
1160 /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes
1161 /// successfully. The contents of this field can be replaced by
1162 /// [`BaseCode::set_packets`].
1163 pub dhcp_ack: Packet,
1164 /// Cached Proxy Offer packet. This field is zero-filled by
1165 /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes
1166 /// successfully. The contents of this field can be replaced by
1167 /// [`BaseCode::set_packets`].
1168 pub proxy_offer: Packet,
1169 /// Cached PXE Discover packet. This field is zero-filled by
1170 /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes
1171 /// successfully. The contents of this field can be replaced by
1172 /// [`BaseCode::set_packets`].
1173 pub pxe_discover: Packet,
1174 /// Cached PXE Reply packet. This field is zero-filled by
1175 /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes
1176 /// successfully. The contents of this field can be replaced by the
1177 /// [`BaseCode::set_packets`] function.
1178 pub pxe_reply: Packet,
1179 /// Cached PXE BIS Reply packet. This field is zero-filled by
1180 /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes
1181 /// successfully. This field can be replaced by [`BaseCode::set_packets`].
1182 pub pxe_bis_reply: Packet,
1183 /// The current IP receive filter settings. The receive filter is disabled
1184 /// and the number of IP receive filters is set to zero by
1185 /// [`BaseCode::start`], and is set by [`BaseCode::set_ip_filter`].
1186 pub ip_filter: IpFilter,
1187 /// The number of valid entries in the ARP cache. This field is reset to
1188 /// zero by [`BaseCode::start`].
1189 pub arp_cache_entries: u32,
1190 /// Array of cached ARP entries.
1191 pub arp_cache: [ArpEntry; 8],
1192 /// The number of valid entries in the current route table. This field is
1193 /// reset to zero by [`BaseCode::start`].
1194 pub route_table_entries: u32,
1195 /// Array of route table entries.
1196 pub route_table: [RouteEntry; 8],
1197 /// ICMP error packet. This field is updated when an ICMP error is received
1198 /// and is undefined until the first ICMP error is received. This field is
1199 /// zero-filled by [`BaseCode::start`].
1200 pub icmp_error: IcmpError,
1201 /// TFTP error packet. This field is updated when a TFTP error is received
1202 /// and is undefined until the first TFTP error is received. This field is
1203 /// zero-filled by the [`BaseCode::start`] function.
1204 pub tftp_error: TftpError,
1205}
1206
1207/// An entry for the ARP cache found in [`Mode::arp_cache`]
1208///
1209/// Corresponds to the `EFI_PXE_BASE_CODE_ARP_ENTRY` type in the C API.
1210#[repr(C)]
1211pub struct ArpEntry {
1212 /// The IP address.
1213 pub ip_addr: IpAddress,
1214 /// The mac address of the device that is addressed by [`Self::ip_addr`].
1215 pub mac_addr: MacAddress,
1216}
1217
1218/// An entry for the route table found in [`Mode::route_table`]
1219///
1220/// Corresponds to the `EFI_PXE_BASE_CODE_ROUTE_ENTRY` type in the C API.
1221#[repr(C)]
1222#[allow(missing_docs)]
1223pub struct RouteEntry {
1224 pub ip_addr: IpAddress,
1225 pub subnet_mask: IpAddress,
1226 pub gw_addr: IpAddress,
1227}
1228
1229/// An ICMP error packet.
1230///
1231/// Corresponds to the `EFI_PXE_BASE_CODE_ICMP_ERROR` type in the C API.
1232#[repr(C)]
1233#[allow(missing_docs)]
1234pub struct IcmpError {
1235 pub ty: u8,
1236 pub code: u8,
1237 pub checksum: u16,
1238 pub u: IcmpErrorUnion,
1239 pub data: [u8; 494],
1240}
1241
1242/// Corresponds to the anonymous union inside
1243/// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API.
1244#[repr(C)]
1245#[allow(missing_docs)]
1246pub union IcmpErrorUnion {
1247 pub reserved: u32,
1248 pub mtu: u32,
1249 pub pointer: u32,
1250 pub echo: IcmpErrorEcho,
1251}
1252
1253/// Corresponds to the `Echo` field in the anonymous union inside
1254/// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API.
1255#[repr(C)]
1256#[derive(Clone, Copy)]
1257#[allow(missing_docs)]
1258pub struct IcmpErrorEcho {
1259 pub identifier: u16,
1260 pub sequence: u16,
1261}
1262
1263/// A TFTP error packet.
1264///
1265/// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_ERROR` type in the C API.
1266#[repr(C)]
1267#[allow(missing_docs)]
1268pub struct TftpError {
1269 pub error_code: u8,
1270 pub error_string: [u8; 127],
1271}
1272
1273/// Returned by [`BaseCode::tftp_read_dir`].
1274#[allow(missing_docs)]
1275pub struct TftpFileInfo<'a> {
1276 pub filename: &'a CStr8,
1277 pub size: u64,
1278 pub year: u16,
1279 pub month: u8,
1280 pub day: u8,
1281 pub hour: u8,
1282 pub minute: u8,
1283 pub second: f32,
1284}
1285
1286/// Returned by [`BaseCode::mtftp_read_dir`].
1287#[allow(missing_docs)]
1288pub struct MtftpFileInfo<'a> {
1289 pub filename: &'a CStr8,
1290 pub ip_address: IpAddress,
1291 pub size: u64,
1292 pub year: u16,
1293 pub month: u8,
1294 pub day: u8,
1295 pub hour: u8,
1296 pub minute: u8,
1297 pub second: f32,
1298}
1299
1300/// Returned if a server sends a malformed response in
1301/// [`BaseCode::tftp_read_dir`] or [`BaseCode::mtftp_read_dir`].
1302#[derive(Clone, Copy, Debug)]
1303pub struct ReadDirParseError;
1304