use core::cell::RefCell;
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use core::str::FromStr;
use at_commands::builder::CommandBuilder;
use at_commands::parser::CommandParser;
use embassy_time::{Duration, Timer};
use heapless::Vec;
use crate::{embassy_net_modem::CAP_SIZE, Error, LteLink};
const DNS_VEC_SIZE: usize = 2;
pub struct Control<'a> {
control: super::Control<'a>,
cid: u8,
lte_link: RefCell<Option<LteLink>>,
needs_reconnect: RefCell<bool>,
}
pub struct PdnAuth<'a> {
pub auth_prot: AuthProt,
pub auth: Option<(&'a [u8], &'a [u8])>,
}
pub struct PdConfig<'a> {
pub apn: Option<&'a [u8]>,
pub pdn_auth: Option<PdnAuth<'a>>,
pub pdp_type: PdpType,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PdpType {
Ip,
Ipv6,
Ipv4v6,
NonIp,
}
impl<'a> From<PdpType> for &'a str {
fn from(val: PdpType) -> &'a str {
match val {
PdpType::Ip => "IP",
PdpType::Ipv6 => "IPV6",
PdpType::Ipv4v6 => "IPV4V6",
PdpType::NonIp => "Non-IP",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AuthProt {
None = 0,
Pap = 1,
Chap = 2,
}
#[derive(Debug, Clone)]
pub struct Status {
pub attached: bool,
pub ipv4_link: Option<LinkInfo<Ipv4Addr>>,
pub ipv6_link: Option<LinkInfo<Ipv6Addr>>,
}
#[derive(Debug, Clone)]
pub struct LinkInfo<AddrType: Clone> {
pub ip: AddrType,
pub gateway: Option<AddrType>,
pub dns: Vec<AddrType, DNS_VEC_SIZE>,
}
#[cfg(feature = "defmt")]
impl defmt::Format for Status {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "attached: {}", self.attached);
if let Some(ipv4_link) = &self.ipv4_link {
defmt::write!(f, ", ipv4: {}", defmt::Debug2Format(&ipv4_link));
}
if let Some(ipv6_link) = &self.ipv6_link {
defmt::write!(f, ", ipv6: {}", defmt::Debug2Format(&ipv6_link));
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CgcontrdpOutputKind {
V4(CgcontrdpOutput<Ipv4Addr>),
V6(CgcontrdpOutput<Ipv6Addr>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct CgcontrdpOutput<AddrType> {
pub gateway: Option<AddrType>,
pub dns: Vec<AddrType, DNS_VEC_SIZE>,
}
fn parse_cgcontrdp_section(at_part: &str) -> Result<Option<CgcontrdpOutputKind>, Error> {
let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) =
CommandParser::parse(at_part.as_bytes())
.expect_int_parameter()
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.finish()?;
let mut is_ipv4 = None;
let gateway = if let Some(ip) = gateway {
if ip.is_empty() {
None
} else {
let parsed = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
match parsed {
IpAddr::V4(_) => {
is_ipv4.replace(true);
}
IpAddr::V6(_) => {
is_ipv4.replace(false);
}
}
Some(parsed)
}
} else {
None
};
const {
assert!(
DNS_VEC_SIZE >= 2,
"Vec holding the DNS must have a capacity of at least 2"
)
}
let mut dns: Vec<IpAddr, DNS_VEC_SIZE> = Vec::new();
if let Some(ip) = dns1 {
if !ip.is_empty() {
let parsed = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
match parsed {
IpAddr::V4(_) => {
is_ipv4.replace(true);
}
IpAddr::V6(_) => {
is_ipv4.replace(false);
}
}
dns.push(parsed).unwrap();
}
}
if let Some(ip) = dns2 {
if !ip.is_empty() {
let parsed = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
match parsed {
IpAddr::V4(_) => {
is_ipv4.replace(true);
}
IpAddr::V6(_) => {
is_ipv4.replace(false);
}
}
dns.push(parsed).unwrap();
}
}
match is_ipv4 {
Some(true) => {
let mut dns_out: Vec<_, DNS_VEC_SIZE> = Vec::new();
for addr in dns.iter() {
dns_out.push(transform_to_v4(*addr)?).unwrap()
}
Ok(Some(CgcontrdpOutputKind::V4(CgcontrdpOutput {
gateway: gateway.map(transform_to_v4).transpose()?,
dns: dns_out,
})))
}
Some(false) => {
let mut dns_out: Vec<_, DNS_VEC_SIZE> = Vec::new();
for addr in dns.iter() {
dns_out.push(transform_to_v6(*addr)?).unwrap()
}
Ok(Some(CgcontrdpOutputKind::V6(CgcontrdpOutput {
gateway: gateway.map(transform_to_v6).transpose()?,
dns: dns_out,
})))
}
None => Ok(None),
}
}
fn transform_to_v4(ip: IpAddr) -> Result<Ipv4Addr, Error> {
match ip {
IpAddr::V4(ip) => Ok(ip),
IpAddr::V6(_) => Err(Error::AddrParseError),
}
}
fn transform_to_v6(ip: IpAddr) -> Result<Ipv6Addr, Error> {
match ip {
IpAddr::V4(_) => Err(Error::AddrParseError),
IpAddr::V6(ip) => Ok(ip),
}
}
impl<'a> Control<'a> {
pub async fn new(control: super::Control<'a>, cid: u8) -> Self {
Self {
control,
cid,
lte_link: RefCell::new(None),
needs_reconnect: RefCell::new(false),
}
}
pub async fn at_command(self, req: &[u8]) -> arrayvec::ArrayString<CAP_SIZE> {
self.control.at_command(req).await
}
pub async fn configure(&self, config: &PdConfig<'_>, pin: Option<&[u8]>) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let link = self.lte_link.borrow_mut().take();
if let Some(link) = link {
link.deactivate().await?;
}
let mut op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGDCONT")
.with_int_parameter(self.cid)
.with_string_parameter::<&str>(config.pdp_type.into());
if let Some(apn) = config.apn {
op = op.with_string_parameter(apn);
}
let op = op.finish().map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
CommandParser::parse(n.as_bytes())
.expect_identifier(b"OK")
.finish()?;
if let Some(pdn_auth) = &config.pdn_auth {
let mut op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGAUTH")
.with_int_parameter(self.cid)
.with_int_parameter(pdn_auth.auth_prot as u8);
if let Some((username, password)) = pdn_auth.auth {
op = op
.with_string_parameter(username)
.with_string_parameter(password);
}
let op = op.finish().map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
CommandParser::parse(n.as_bytes())
.expect_identifier(b"OK")
.finish()?;
}
if let Some(pin) = pin {
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CPIN")
.with_string_parameter(pin)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let _ = self.control.at_command(op).await;
}
Ok(())
}
pub async fn attach(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGATT")
.with_int_parameter(1)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
CommandParser::parse(n.as_bytes())
.expect_identifier(b"OK")
.finish()?;
Ok(())
}
pub async fn detach(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGATT")
.with_int_parameter(0)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
CommandParser::parse(n.as_bytes())
.expect_identifier(b"OK")
.finish()?;
Ok(())
}
async fn attached(&self) -> Result<bool, Error> {
if self.lte_link.borrow().is_none() {
return Ok(false);
}
let mut cmd: [u8; 256] = [0; 256];
let op = CommandBuilder::create_query(&mut cmd, true)
.named("+CGATT")
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
let (res,) = CommandParser::parse(n.as_bytes())
.expect_identifier(b"+CGATT: ")
.expect_int_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
Ok(res == 1)
}
pub async fn status(&self) -> Result<Status, Error> {
let mut cmd: [u8; 256] = [0; 256];
let op = CommandBuilder::create_query(&mut cmd, true)
.named("+CGATT")
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
let (res,) = CommandParser::parse(n.as_bytes())
.expect_identifier(b"+CGATT: ")
.expect_int_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
let attached = res == 1;
if !attached {
return Ok(Status {
attached,
ipv4_link: None,
ipv6_link: None,
});
}
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGPADDR")
.with_int_parameter(self.cid)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
let (_, ip1, ip2) = CommandParser::parse(n.as_bytes())
.expect_identifier(b"+CGPADDR: ")
.expect_int_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
let mut ipv4 = None;
let mut ipv6 = None;
if let Some(ip) = ip1 {
match IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)? {
IpAddr::V4(ip) => {
let _ = ipv4.replace(ip);
}
IpAddr::V6(ip) => {
let _ = ipv6.replace(ip);
}
};
}
if let Some(ip) = ip2 {
match IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)? {
IpAddr::V4(_) => {
return Err(Error::AddrParseError);
}
IpAddr::V6(ip) => {
let _ = ipv6.replace(ip);
}
};
}
#[cfg(feature = "defmt")]
defmt::debug!("IPv4: {:?}, IPv6: {:?}", ipv4, ipv6);
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGCONTRDP")
.with_int_parameter(self.cid)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
let mut sections = n.as_str().split("+CGCONTRDP: ");
sections.next();
let mut ipv4_link = ipv4.map(|ip| LinkInfo {
ip,
dns: Vec::new(),
gateway: None,
});
let mut ipv6_link = ipv6.map(|ip| LinkInfo {
ip,
dns: Vec::new(),
gateway: None,
});
let section = sections.next().ok_or(Error::UnexpectedAtResponse)?;
let output = parse_cgcontrdp_section(section)?;
match output {
Some(CgcontrdpOutputKind::V4(output)) => {
ipv4_link = ipv4_link.map(|l| LinkInfo {
ip: l.ip,
gateway: output.gateway,
dns: output.dns,
});
}
Some(CgcontrdpOutputKind::V6(output)) => {
ipv6_link = ipv6_link.map(|l| LinkInfo {
ip: l.ip,
gateway: output.gateway,
dns: output.dns,
});
}
None => {
}
}
if let Some(section) = sections.next() {
let output = parse_cgcontrdp_section(section)?;
match output {
Some(CgcontrdpOutputKind::V4(_)) => {
return Err(Error::UnexpectedAtResponse);
}
Some(CgcontrdpOutputKind::V6(output)) => {
ipv6_link = ipv6_link.map(|l| LinkInfo {
ip: l.ip,
gateway: output.gateway,
dns: output.dns,
})
}
None => {
}
}
}
Ok(Status {
attached,
ipv4_link,
ipv6_link,
})
}
async fn wait_attached(&self) -> Result<Status, Error> {
while !self.attached().await? {
Timer::after(Duration::from_secs(1)).await;
}
let status = self.status().await?;
Ok(status)
}
pub async fn disable(&self) -> Result<(), Error> {
let link = self.lte_link.borrow_mut().take();
if let Some(link) = link {
link.deactivate().await?;
};
self.control.close_raw_socket().await;
*self.needs_reconnect.borrow_mut() = true;
Ok(())
}
pub async fn enable(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
if self.lte_link.borrow().is_none() {
let link = LteLink::new().await?;
self.lte_link.borrow_mut().replace(link);
}
let op = CommandBuilder::create_set(&mut cmd, true)
.named("%XPDNCFG")
.with_int_parameter(1)
.finish()
.map_err(|s| Error::BufferTooSmall(Some(s)))?;
let n = self.control.at_command(op).await;
CommandParser::parse(n.as_bytes())
.expect_identifier(b"OK")
.finish()?;
Ok(())
}
pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> {
self.enable().await?;
let status = self.wait_attached().await?;
self.control.open_raw_socket().await;
reattach(&status);
loop {
if !self.attached().await? || *self.needs_reconnect.borrow() {
self.control.close_raw_socket().await;
let status = self.wait_attached().await?;
self.control.open_raw_socket().await;
*self.needs_reconnect.borrow_mut() = false;
reattach(&status);
}
Timer::after(Duration::from_secs(10)).await;
}
}
}