use std::fmt::{Display, Formatter};
use std::{
convert::Infallible,
fmt::Debug,
mem::MaybeUninit,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
os::raw::c_int,
slice::Iter,
str::FromStr,
vec::Drain,
};
use libc::{c_ushort, in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t, AF_INET, AF_INET6};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use url::{Host, Url};
use libcoap_sys::{
coap_address_t, coap_mid_t, coap_proto_t,
coap_proto_t::{COAP_PROTO_DTLS, COAP_PROTO_NONE, COAP_PROTO_TCP, COAP_PROTO_TLS, COAP_PROTO_UDP},
coap_uri_scheme_t,
coap_uri_scheme_t::{
COAP_URI_SCHEME_COAP, COAP_URI_SCHEME_COAPS, COAP_URI_SCHEME_COAPS_TCP, COAP_URI_SCHEME_COAP_TCP,
COAP_URI_SCHEME_HTTP, COAP_URI_SCHEME_HTTPS,
},
COAP_URI_SCHEME_SECURE_MASK,
};
use crate::error::UriParsingError;
pub type IfIndex = c_int;
pub type MaxRetransmit = c_ushort;
pub type CoapMessageId = coap_mid_t;
pub(crate) struct CoapAddress(coap_address_t);
impl CoapAddress {
pub(crate) fn as_raw_address(&self) -> &coap_address_t {
&self.0
}
#[allow(dead_code)]
pub(crate) unsafe fn as_mut_raw_address(&mut self) -> &mut coap_address_t {
&mut self.0
}
#[allow(dead_code)]
pub(crate) fn into_raw_address(self) -> coap_address_t {
self.0
}
}
impl ToSocketAddrs for CoapAddress {
type Iter = std::option::IntoIter<SocketAddr>;
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
let socketaddr = match unsafe { self.0.addr.sa.as_ref().sa_family } as i32 {
AF_INET => {
let raw_addr = unsafe { self.0.addr.sin.as_ref() };
SocketAddrV4::new(
Ipv4Addr::from(raw_addr.sin_addr.s_addr.to_ne_bytes()),
u16::from_be(raw_addr.sin_port),
)
.into()
},
AF_INET6 => {
let raw_addr = unsafe { self.0.addr.sin6.as_ref() };
SocketAddrV6::new(
Ipv6Addr::from(raw_addr.sin6_addr.s6_addr),
u16::from_be(raw_addr.sin6_port),
raw_addr.sin6_flowinfo,
raw_addr.sin6_scope_id,
)
.into()
},
_ => panic!("sa_family_t of underlying coap_address_t is invalid!"),
};
Ok(Some(socketaddr).into_iter())
}
}
impl From<SocketAddr> for CoapAddress {
fn from(addr: SocketAddr) -> Self {
match addr {
SocketAddr::V4(addr) => {
unsafe {
let mut coap_addr = coap_address_t {
size: std::mem::size_of::<sockaddr_in>() as socklen_t,
addr: std::mem::zeroed(),
};
*coap_addr.addr.sin.as_mut() = sockaddr_in {
sin_family: AF_INET as sa_family_t,
sin_port: addr.port().to_be(),
sin_addr: in_addr {
s_addr: u32::from_ne_bytes(addr.ip().octets()),
},
sin_zero: Default::default(),
};
CoapAddress(coap_addr)
}
},
SocketAddr::V6(addr) => {
unsafe {
let mut coap_addr = coap_address_t {
size: std::mem::size_of::<sockaddr_in6>() as socklen_t,
addr: std::mem::zeroed(),
};
*coap_addr.addr.sin6.as_mut() = sockaddr_in6 {
sin6_family: AF_INET6 as sa_family_t,
sin6_port: addr.port().to_be(),
sin6_addr: in6_addr {
s6_addr: addr.ip().octets(),
},
sin6_flowinfo: addr.flowinfo(),
sin6_scope_id: addr.scope_id(),
};
CoapAddress(coap_addr)
}
},
}
}
}
#[doc(hidden)]
impl From<coap_address_t> for CoapAddress {
fn from(raw_addr: coap_address_t) -> Self {
CoapAddress(raw_addr)
}
}
#[doc(hidden)]
impl From<&coap_address_t> for CoapAddress {
fn from(raw_addr: &coap_address_t) -> Self {
let mut new_addr = MaybeUninit::zeroed();
unsafe {
std::ptr::copy_nonoverlapping(raw_addr, new_addr.as_mut_ptr(), 1);
CoapAddress(new_addr.assume_init())
}
}
}
#[repr(u32)]
#[derive(Copy, Clone, FromPrimitive, Debug, PartialEq, Eq, Hash)]
pub enum CoapUriScheme {
Coap = COAP_URI_SCHEME_COAP as u32,
Coaps = COAP_URI_SCHEME_COAPS as u32,
CoapTcp = COAP_URI_SCHEME_COAP_TCP as u32,
CoapsTcp = COAP_URI_SCHEME_COAPS_TCP as u32,
Http = COAP_URI_SCHEME_HTTP as u32,
Https = COAP_URI_SCHEME_HTTPS as u32,
}
impl CoapUriScheme {
pub fn is_secure(self) -> bool {
COAP_URI_SCHEME_SECURE_MASK & (self as u32) > 0
}
pub fn from_raw_scheme(scheme: coap_uri_scheme_t) -> CoapUriScheme {
num_traits::FromPrimitive::from_u32(scheme as u32).expect("unknown scheme")
}
}
impl FromStr for CoapUriScheme {
type Err = UriParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"coap" => Ok(CoapUriScheme::Coap),
"coaps" => Ok(CoapUriScheme::Coaps),
"coap+tcp" => Ok(CoapUriScheme::CoapTcp),
"coaps+tcp" => Ok(CoapUriScheme::CoapsTcp),
"http" => Ok(CoapUriScheme::Http),
"https" => Ok(CoapUriScheme::Https),
_ => Err(UriParsingError::NotACoapScheme),
}
}
}
impl Display for CoapUriScheme {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
CoapUriScheme::Coap => "coap",
CoapUriScheme::Coaps => "coaps",
CoapUriScheme::CoapTcp => "coap+tcp",
CoapUriScheme::CoapsTcp => "coaps+tcp",
CoapUriScheme::Http => "http",
CoapUriScheme::Https => "https",
})
}
}
impl From<coap_uri_scheme_t> for CoapUriScheme {
fn from(scheme: coap_uri_scheme_t) -> Self {
CoapUriScheme::from_raw_scheme(scheme)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CoapUriHost {
IpLiteral(IpAddr),
Name(String),
}
impl<T: ToString> From<url::Host<T>> for CoapUriHost {
fn from(host: Host<T>) -> Self {
match host {
Host::Domain(d) => CoapUriHost::Name(d.to_string()),
Host::Ipv4(addr) => CoapUriHost::IpLiteral(IpAddr::V4(addr)),
Host::Ipv6(addr) => CoapUriHost::IpLiteral(IpAddr::V6(addr)),
}
}
}
impl FromStr for CoapUriHost {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(IpAddr::from_str(s).map_or_else(|_| CoapUriHost::Name(s.to_string()), CoapUriHost::IpLiteral))
}
}
impl Display for CoapUriHost {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(
match self {
CoapUriHost::IpLiteral(addr) => addr.to_string(),
CoapUriHost::Name(host) => host.clone(),
}
.as_str(),
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CoapUri {
scheme: Option<CoapUriScheme>,
host: Option<CoapUriHost>,
port: Option<u16>,
path: Option<Vec<String>>,
query: Option<Vec<String>>,
}
impl CoapUri {
pub fn new(
scheme: Option<CoapUriScheme>,
host: Option<CoapUriHost>,
port: Option<u16>,
path: Option<Vec<String>>,
query: Option<Vec<String>>,
) -> CoapUri {
CoapUri {
scheme,
host,
port,
path,
query,
}
}
pub fn try_from_url(url: Url) -> Result<CoapUri, UriParsingError> {
let path: Vec<String> = url
.path()
.split('/')
.map(String::from)
.filter(|v| !v.is_empty())
.collect();
let path = if path.is_empty() { None } else { Some(path) };
let query: Vec<String> = url.query_pairs().map(|(k, v)| format!("{}={}", k, v)).collect();
let query = if query.is_empty() { None } else { Some(query) };
Ok(CoapUri {
scheme: Some(CoapUriScheme::from_str(url.scheme())?),
host: url.host().map(|h| h.into()),
port: url.port(),
path,
query,
})
}
pub fn scheme(&self) -> Option<&CoapUriScheme> {
self.scheme.as_ref()
}
pub fn host(&self) -> Option<&CoapUriHost> {
self.host.as_ref()
}
pub fn port(&self) -> Option<u16> {
self.port
}
pub(crate) fn drain_path_iter(&mut self) -> Option<Drain<String>> {
self.path.as_mut().map(|p| p.drain(..))
}
pub fn path_iter(&self) -> Option<Iter<'_, String>> {
self.path.as_ref().map(|p| p.iter())
}
pub fn drain_query_iter(&mut self) -> Option<Drain<String>> {
self.query.as_mut().map(|p| p.drain(..))
}
pub fn query_iter(&self) -> Option<Iter<String>> {
self.query.as_ref().map(|p| p.iter())
}
}
impl TryFrom<Url> for CoapUri {
type Error = UriParsingError;
fn try_from(value: Url) -> Result<Self, Self::Error> {
CoapUri::try_from_url(value)
}
}
impl Display for CoapUri {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}{}{}{}{}",
self.scheme.map_or_else(String::new, |v| format!("{}://", v)),
self.host.as_ref().map_or_else(String::new, |v| v.to_string()),
self.port.map_or_else(String::new, |v| format!(":{}", v)),
self.path
.as_ref()
.map_or_else(String::new, |v| format!("/{}", v.join("/"))),
self.query
.as_ref()
.map_or_else(String::new, |v| format!("?{}", v.join("&"))),
))
}
}
#[repr(u32)]
#[non_exhaustive]
#[derive(Copy, Clone, FromPrimitive, PartialEq, Eq, Hash)]
pub enum CoapProtocol {
None = COAP_PROTO_NONE as u32,
Udp = COAP_PROTO_UDP as u32,
Dtls = COAP_PROTO_DTLS as u32,
Tcp = COAP_PROTO_TCP as u32,
Tls = COAP_PROTO_TLS as u32,
}
#[doc(hidden)]
impl From<coap_proto_t> for CoapProtocol {
fn from(raw_proto: coap_proto_t) -> Self {
<CoapProtocol as FromPrimitive>::from_u32(raw_proto as u32).expect("unknown protocol")
}
}
impl Display for CoapProtocol {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
CoapProtocol::None => "none",
CoapProtocol::Udp => "udp",
CoapProtocol::Dtls => "dtls",
CoapProtocol::Tcp => "tcp",
CoapProtocol::Tls => "tls",
})
}
}
fn convert_to_fixed_size_slice(n: usize, val: &[u8]) -> Box<[u8]> {
if val.len() > n {
panic!("supplied slice too short");
}
let mut buffer: Vec<u8> = vec![0; n];
let (_, target_buffer) = buffer.split_at_mut(n - val.len());
target_buffer.copy_from_slice(val);
buffer.truncate(n);
buffer.into_boxed_slice()
}
pub(crate) fn decode_var_len_u32(val: &[u8]) -> u32 {
u32::from_be_bytes(
convert_to_fixed_size_slice(4, val)[..4]
.try_into()
.expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
)
}
pub(crate) fn encode_var_len_u32(val: u32) -> Box<[u8]> {
let bytes_to_discard = val.leading_zeros() / 8;
let mut ret_val = Vec::from(val.to_be_bytes());
ret_val.drain(..bytes_to_discard as usize);
ret_val.into_boxed_slice()
}
#[allow(unused)]
pub(crate) fn decode_var_len_u64(val: &[u8]) -> u64 {
u64::from_be_bytes(
convert_to_fixed_size_slice(8, val)[..8]
.try_into()
.expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
)
}
#[allow(unused)]
pub(crate) fn encode_var_len_u64(val: u64) -> Box<[u8]> {
let bytes_to_discard = val.leading_zeros() / 8;
let mut ret_val = Vec::from(val.to_be_bytes());
ret_val.drain(..bytes_to_discard as usize);
ret_val.into_boxed_slice()
}
pub(crate) fn decode_var_len_u16(val: &[u8]) -> u16 {
u16::from_be_bytes(
convert_to_fixed_size_slice(2, val)[..2]
.try_into()
.expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
)
}
pub(crate) fn encode_var_len_u16(val: u16) -> Box<[u8]> {
let bytes_to_discard = val.leading_zeros() / 8;
let mut ret_val = Vec::from(val.to_be_bytes());
ret_val.drain(..bytes_to_discard as usize);
ret_val.into_boxed_slice()
}
#[allow(unused)]
pub(crate) fn decode_var_len_u8(val: &[u8]) -> u16 {
u16::from_be_bytes(
convert_to_fixed_size_slice(1, val)[..1]
.try_into()
.expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
)
}
pub(crate) fn encode_var_len_u8(val: u8) -> Box<[u8]> {
Vec::from([val]).into_boxed_slice()
}