use core::fmt::Debug;
use core::net::Ipv4Addr;
use edge_nal::{UdpReceive, UdpSend};
use embassy_futures::select::{select, Either};
use embassy_time::{Duration, Instant, Timer};
use rand_core::RngCore;
pub use super::*;
pub use crate::Settings;
use crate::{Options, Packet};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct NetworkInfo<'a> {
pub gateway: Option<Ipv4Addr>,
pub subnet: Option<Ipv4Addr>,
pub dns1: Option<Ipv4Addr>,
pub dns2: Option<Ipv4Addr>,
pub captive_url: Option<&'a str>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Lease {
pub ip: Ipv4Addr,
pub server_ip: Ipv4Addr,
pub duration: Duration,
pub acquired: Instant,
}
impl Lease {
pub async fn new<'a, T, S>(
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &'a mut [u8],
) -> Result<(Self, NetworkInfo<'a>), Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
loop {
let offer = Self::discover(client, socket, buf, Duration::from_secs(3)).await?;
let server_ip = unwrap!(offer.server_ip);
let ip = offer.ip;
let now = Instant::now();
{
let buf = unsafe { Self::unsafe_reborrow(buf) };
if let Some(settings) = Self::request(
client,
socket,
buf,
server_ip,
ip,
true,
Duration::from_secs(3),
3,
)
.await?
{
break Ok((
Self {
ip: settings.ip,
server_ip: unwrap!(settings.server_ip),
duration: Duration::from_secs(
settings.lease_time_secs.unwrap_or(7200) as _
),
acquired: now,
},
NetworkInfo {
gateway: settings.gateway,
subnet: settings.subnet,
dns1: settings.dns1,
dns2: settings.dns2,
captive_url: settings.captive_url,
},
));
}
}
}
}
pub async fn keep<T, S>(
&mut self,
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &mut [u8],
) -> Result<(), Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
loop {
let now = Instant::now();
if now - self.acquired >= self.duration / 3 {
if !self.renew(client, socket, buf).await? {
break;
}
} else {
Timer::after(Duration::from_secs(60)).await;
}
}
Ok(())
}
pub async fn renew<T, S>(
&mut self,
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &mut [u8],
) -> Result<bool, Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
info!("Renewing DHCP lease...");
let now = Instant::now();
let settings = Self::request(
client,
socket,
buf,
self.server_ip,
self.ip,
false,
Duration::from_secs(3),
3,
)
.await?;
if let Some(settings) = settings.as_ref() {
self.duration = settings
.lease_time_secs
.map(|lt| Duration::from_secs(lt as _))
.unwrap_or(self.duration);
self.acquired = now;
}
Ok(settings.is_some())
}
pub async fn release<T, S>(
self,
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &mut [u8],
) -> Result<(), Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
let mut opt_buf = Options::buf();
let request = client.release(&mut opt_buf, 0, self.ip);
socket
.send(
SocketAddr::V4(SocketAddrV4::new(self.server_ip, DEFAULT_SERVER_PORT)),
request.encode(buf)?,
)
.await
.map_err(Error::Io)?;
Ok(())
}
async fn discover<'a, T, S>(
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &'a mut [u8],
timeout: Duration,
) -> Result<Settings<'a>, Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
info!("Discovering DHCP servers...");
let start = Instant::now();
loop {
let mut opt_buf = Options::buf();
let (request, xid) =
client.discover(&mut opt_buf, (Instant::now() - start).as_secs() as _, None);
socket
.send(
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::BROADCAST, DEFAULT_SERVER_PORT)),
request.encode(buf)?,
)
.await
.map_err(Error::Io)?;
if let Either::First(result) = select(socket.receive(buf), Timer::after(timeout)).await
{
let buf = unsafe { Self::unsafe_reborrow(buf) };
let (len, _remote) = result.map_err(Error::Io)?;
let reply = Packet::decode(&buf[..len])?;
if client.is_offer(&reply, xid) {
let settings = Settings::new(&reply);
info!(
"IP {} offered by DHCP server {}",
settings.ip,
unwrap!(settings.server_ip)
);
return Ok(settings);
}
}
info!("No DHCP offers received, retrying...");
}
}
#[allow(clippy::too_many_arguments)]
async fn request<'a, T, S>(
client: &mut dhcp::client::Client<T>,
socket: &mut S,
buf: &'a mut [u8],
server_ip: Ipv4Addr,
ip: Ipv4Addr,
broadcast: bool,
timeout: Duration,
retries: usize,
) -> Result<Option<Settings<'a>>, Error<S::Error>>
where
T: RngCore,
S: UdpReceive + UdpSend,
{
for _ in 0..retries {
info!("Requesting IP {} from DHCP server {}", ip, server_ip);
let start = Instant::now();
let mut opt_buf = Options::buf();
let (request, xid) = client.request(
&mut opt_buf,
(Instant::now() - start).as_secs() as _,
ip,
broadcast,
);
socket
.send(
SocketAddr::V4(SocketAddrV4::new(
if broadcast {
Ipv4Addr::BROADCAST
} else {
server_ip
},
DEFAULT_SERVER_PORT,
)),
request.encode(buf)?,
)
.await
.map_err(Error::Io)?;
if let Either::First(result) = select(socket.receive(buf), Timer::after(timeout)).await
{
let (len, _remote) = result.map_err(Error::Io)?;
let buf = unsafe { Self::unsafe_reborrow(buf) };
let packet = &buf[..len];
let reply = Packet::decode(packet)?;
if client.is_ack(&reply, xid) {
let settings = Settings::new(&reply);
info!("IP {} leased successfully", ip);
return Ok(Some(settings));
} else if client.is_nak(&reply, xid) {
info!("IP {} not acknowledged", ip);
return Ok(None);
}
}
}
warn!("IP request was not replied");
Ok(None)
}
unsafe fn unsafe_reborrow<'a>(buf: &mut [u8]) -> &'a mut [u8] {
let len = buf.len();
unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), len) }
}
}