use crate::server::coap::codec::{self, CoapMessage, MessageType};
use anyhow::{bail, Context, Result};
use std::collections::{HashMap, HashSet, VecDeque};
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU16, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::sync::{mpsc, oneshot};
pub const OPT_OBSERVE: u16 = 6;
pub const MAX_DATAGRAM: usize = 16 * 1024;
const ACK_TIMEOUT: Duration = Duration::from_secs(2);
const MAX_RETRANSMIT: u32 = 4;
const SEPARATE_WAIT: Duration = Duration::from_secs(30);
const DEDUP_ENTRIES: usize = 512;
const OBSERVATIONS: usize = 1024;
type Key = (SocketAddr, Vec<u8>);
pub struct Exchange {
socket: Arc<UdpSocket>,
connected: Option<SocketAddr>,
refused: std::sync::atomic::AtomicBool,
pending: Mutex<HashMap<Key, oneshot::Sender<CoapMessage>>>,
acked: Mutex<HashMap<(SocketAddr, u16), oneshot::Sender<()>>>,
observations: Mutex<HashMap<Key, mpsc::Sender<CoapMessage>>>,
recent: Mutex<VecDeque<((SocketAddr, u16), Vec<u8>)>>,
inflight: Mutex<HashSet<(SocketAddr, u16)>>,
mid: AtomicU16,
token: AtomicU64,
}
impl Exchange {
pub fn new(socket: Arc<UdpSocket>) -> Arc<Self> {
let seed = crate::utils::clock::SystemTime::now()
.duration_since(crate::utils::clock::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
Arc::new(Self {
socket,
connected: None,
refused: std::sync::atomic::AtomicBool::new(false),
pending: Mutex::new(HashMap::new()),
acked: Mutex::new(HashMap::new()),
observations: Mutex::new(HashMap::new()),
recent: Mutex::new(VecDeque::new()),
inflight: Mutex::new(HashSet::new()),
mid: AtomicU16::new(seed as u16),
token: AtomicU64::new(seed),
})
}
pub fn connected(socket: Arc<UdpSocket>, peer: SocketAddr) -> Arc<Self> {
let mut ex = Arc::into_inner(Self::new(socket)).expect("a new exchange has one owner");
ex.connected = Some(peer);
Arc::new(ex)
}
async fn transmit(&self, bytes: &[u8], peer: SocketAddr) -> std::io::Result<()> {
match self.connected {
Some(c) if c == peer => self.socket.send(bytes).await.map(|_| ()),
Some(_) => Err(std::io::Error::other(
"this exchange talks to one peer only",
)),
None => self.socket.send_to(bytes, peer).await.map(|_| ()),
}
}
async fn receive(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)> {
match self.connected {
Some(c) => self.socket.recv(buf).await.map(|n| (n, c)),
None => self.socket.recv_from(buf).await,
}
}
pub fn socket(&self) -> &UdpSocket {
&self.socket
}
fn next_mid(&self) -> u16 {
self.mid.fetch_add(1, Ordering::Relaxed)
}
async fn send(&self, peer: SocketAddr, m: &CoapMessage) -> Result<()> {
let bytes = m.encode().map_err(|e| anyhow::anyhow!("{e}"))?;
self.transmit(&bytes, peer).await?;
Ok(())
}
pub async fn request(
&self,
peer: SocketAddr,
code: u8,
options: Vec<(u16, Vec<u8>)>,
payload: Vec<u8>,
observe: Option<mpsc::Sender<CoapMessage>>,
) -> Result<CoapMessage> {
let token = self
.token
.fetch_add(1, Ordering::Relaxed)
.to_be_bytes()
.to_vec();
let mid = self.next_mid();
let m = CoapMessage {
mtype: MessageType::Confirmable,
code,
message_id: mid,
token: token.clone(),
options,
payload,
};
let (tx, mut rx) = oneshot::channel();
let (ack_tx, mut ack_rx) = oneshot::channel();
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert((peer, token.clone()), tx);
self.acked
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert((peer, mid), ack_tx);
if let Some(sink) = &observe {
let mut obs = self.observations.lock().unwrap_or_else(|e| e.into_inner());
if obs.len() < OBSERVATIONS {
obs.insert((peer, token.clone()), sink.clone());
}
}
let result = self.await_response(peer, &m, &mut rx, &mut ack_rx).await;
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(peer, token.clone()));
self.acked
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(peer, mid));
let observing = matches!(&result, Ok(r) if r.option_values(OPT_OBSERVE).first().is_some() && codec::code_class(r.code) == 2);
if observe.is_some() && !observing {
self.forget(peer, &token);
}
result
}
async fn await_response(
&self,
peer: SocketAddr,
m: &CoapMessage,
rx: &mut oneshot::Receiver<CoapMessage>,
ack_rx: &mut oneshot::Receiver<()>,
) -> Result<CoapMessage> {
let mut wait = ACK_TIMEOUT;
for _ in 0..=MAX_RETRANSMIT {
self.send(peer, m).await?;
tokio::select! {
r = &mut *rx => {
if r.is_err() && self.refused.load(Ordering::Relaxed) {
bail!("{peer} refused: nothing listens on that port");
}
return r.context("the exchange was dropped");
}
a = &mut *ack_rx => {
if a.is_ok() {
return tokio::time::timeout(SEPARATE_WAIT, rx)
.await
.context("no separate response followed the ACK")?
.context("the exchange was dropped");
}
}
_ = tokio::time::sleep(wait) => wait *= 2,
}
}
bail!("{peer} did not answer")
}
pub fn forget(&self, peer: SocketAddr, token: &[u8]) {
self.observations
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(peer, token.to_vec()));
}
pub async fn respond(
&self,
peer: SocketAddr,
request: &CoapMessage,
code: u8,
options: Vec<(u16, Vec<u8>)>,
payload: Vec<u8>,
) -> Result<()> {
let (mtype, mid) = if request.mtype == MessageType::Confirmable {
(MessageType::Acknowledgement, request.message_id)
} else {
(MessageType::NonConfirmable, self.next_mid())
};
let m = CoapMessage {
mtype,
code,
message_id: mid,
token: request.token.clone(),
options,
payload,
};
let bytes = m.encode().map_err(|e| anyhow::anyhow!("{e}"))?;
self.transmit(&bytes, peer).await?;
if request.mtype == MessageType::Confirmable {
self.inflight
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(peer, request.message_id));
let mut recent = self.recent.lock().unwrap_or_else(|e| e.into_inner());
if recent.len() >= DEDUP_ENTRIES {
recent.pop_front();
}
recent.push_back(((peer, request.message_id), bytes));
}
Ok(())
}
pub async fn notify(
&self,
peer: SocketAddr,
token: &[u8],
sequence: u32,
options: Vec<(u16, Vec<u8>)>,
payload: Vec<u8>,
) -> Result<()> {
let mut opts = vec![(
OPT_OBSERVE,
(sequence & 0x00FF_FFFF).to_be_bytes()[1..].to_vec(),
)];
opts.extend(options);
let m = CoapMessage {
mtype: MessageType::NonConfirmable,
code: codec::CODE_CONTENT,
message_id: self.next_mid(),
token: token.to_vec(),
options: opts,
payload,
};
self.send(peer, &m).await
}
pub async fn run(self: Arc<Self>, requests: mpsc::Sender<(SocketAddr, CoapMessage)>) {
let mut buf = vec![0u8; MAX_DATAGRAM + 1];
loop {
let (n, peer) = match self.receive(&mut buf).await {
Ok(r) => r,
Err(e) if e.kind() == std::io::ErrorKind::ConnectionRefused => {
self.refused.store(true, Ordering::Relaxed);
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.clear();
continue;
}
Err(_) => continue,
};
self.refused.store(false, Ordering::Relaxed);
if n > MAX_DATAGRAM {
continue;
}
let Ok(m) = CoapMessage::decode(&buf[..n]) else {
continue;
};
if m.is_request() {
if m.mtype == MessageType::Confirmable {
let cached = self
.recent
.lock()
.unwrap_or_else(|e| e.into_inner())
.iter()
.find(|(k, _)| *k == (peer, m.message_id))
.map(|(_, b)| b.clone());
if let Some(bytes) = cached {
let _ = self.transmit(&bytes, peer).await;
continue;
}
let mut inflight = self.inflight.lock().unwrap_or_else(|e| e.into_inner());
if !inflight.insert((peer, m.message_id)) || inflight.len() > DEDUP_ENTRIES {
inflight.remove(&(peer, m.message_id));
continue;
}
}
if requests.send((peer, m)).await.is_err() {
return;
}
continue;
}
match m.mtype {
MessageType::Acknowledgement | MessageType::Reset if m.is_empty_message() => {
if let Some(tx) = self
.acked
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(peer, m.message_id))
{
let _ = tx.send(());
}
if m.mtype == MessageType::Reset {
self.observations
.lock()
.unwrap_or_else(|e| e.into_inner())
.retain(|(p, _), _| *p != peer);
}
continue;
}
_ => {}
}
if m.mtype == MessageType::Confirmable {
let ack = CoapMessage {
mtype: MessageType::Acknowledgement,
code: codec::CODE_EMPTY,
message_id: m.message_id,
token: vec![],
options: vec![],
payload: vec![],
};
let _ = self.send(peer, &ack).await;
}
let key = (peer, m.token.clone());
if let Some(tx) = self
.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&key)
{
let _ = tx.send(m);
continue;
}
let sink = self
.observations
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&key)
.cloned();
match sink {
Some(s) if s.try_send(m.clone()).is_ok() || !s.is_closed() => {}
_ => {
self.forget(peer, &key.1);
let rst = CoapMessage {
mtype: MessageType::Reset,
code: codec::CODE_EMPTY,
message_id: m.message_id,
token: vec![],
options: vec![],
payload: vec![],
};
let _ = self.send(peer, &rst).await;
}
}
}
}
}
pub fn path_options(path: &str) -> Vec<(u16, Vec<u8>)> {
path.split('/')
.filter(|s| !s.is_empty())
.map(|s| (codec::OPT_URI_PATH, s.as_bytes().to_vec()))
.collect()
}
pub fn uint_option(number: u16, v: u32) -> (u16, Vec<u8>) {
let b = v.to_be_bytes();
let skip = b.iter().take_while(|x| **x == 0).count();
(number, b[skip..].to_vec())
}