1pub mod actions;
8pub mod blockwise;
9pub mod codec;
10pub mod observe;
11
12use anyhow::Result;
13use std::net::SocketAddr;
14use std::sync::atomic::{AtomicU16, Ordering};
15use std::sync::{Arc, Mutex};
16use std::time::Duration;
17use tokio::net::UdpSocket;
18use tokio::sync::mpsc;
19use tracing::{debug, error, info, warn};
20
21use crate::llm::action_helper::call_llm;
22use crate::llm::ollama_client::OllamaClient;
23use crate::llm::ActionResult;
24use crate::logging::emit::Log;
25use crate::protocol::Event;
26use crate::server::connection::ConnectionId;
27use crate::state::app_state::AppState;
28use crate::utils::clock::Instant;
29use crate::{console_debug, console_trace};
30
31use actions::{CoapProtocol, COAP_REQUEST_EVENT, RESULT_IGNORE, RESULT_RESET, RESULT_RESPONSE};
32use blockwise::{Representation, ResourceKey, Transfers, UploadKey, UploadStep, Uploads};
33use codec::{BlockValue, CoapMessage, MessageType};
34use observe::{Observer, Observers, Registered};
35
36pub const DEFAULT_NOTIFICATION_ACK_TIMEOUT_MS: u64 = 2000;
39
40pub const NOTIFICATION_MAX_RETRANSMIT: u32 = 4;
43
44pub struct CoapServer;
46
47pub struct CoapServerHandle {
53 socket: Arc<UdpSocket>,
54 server_id: crate::state::ServerId,
55 status_tx: mpsc::UnboundedSender<String>,
56 next_message_id: AtomicU16,
58 observers: Mutex<Observers>,
59 transfers: Mutex<Transfers>,
60 uploads: Mutex<Uploads>,
61 ack_timeout: Duration,
62}
63
64#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct NotifyReport {
67 pub sent: usize,
69 pub ended: usize,
71}
72
73impl CoapServerHandle {
74 fn fresh_message_id(&self) -> u16 {
75 self.next_message_id.fetch_add(1, Ordering::Relaxed)
76 }
77
78 pub fn observers(&self) -> Vec<Observer> {
80 self.expire_observers();
81 lock(&self.observers).all()
82 }
83
84 pub fn pending_notifications(&self) -> usize {
86 lock(&self.observers).pending_len()
87 }
88
89 fn expire_observers(&self) {
91 let expired = lock(&self.observers).expire(Instant::now());
92 for observer in expired {
93 info!(
94 "CoAP observer {} of {} unconfirmed for {} s; observation removed decision={}",
95 observer.peer,
96 observer.path,
97 observe::OBSERVER_MAX_AGE.as_secs(),
98 Decision::ObserverExpired.as_str()
99 );
100 }
101 }
102
103 pub fn cached_transfers(&self) -> usize {
105 lock(&self.transfers).len()
106 }
107
108 pub fn uploads_in_progress(&self) -> usize {
110 lock(&self.uploads).len()
111 }
112
113 pub async fn notify(
125 &self,
126 app_state: &AppState,
127 path: &str,
128 rep: Representation,
129 confirmable: bool,
130 ) -> NotifyReport {
131 let rep = Arc::new(rep);
132 self.expire_observers();
133 let observers = lock(&self.observers).of_path(path);
134 let mut report = NotifyReport::default();
135 for observer in observers {
136 let keeps = codec::code_class(rep.code) == 2;
137 let confirmable = confirmable
138 || (keeps
139 && lock(&self.observers).needs_confirmation(
140 observer.peer,
141 &observer.token,
142 Instant::now(),
143 ));
144 let mid = self.fresh_message_id();
145 let mut message = CoapMessage {
146 mtype: if confirmable {
147 MessageType::Confirmable
148 } else {
149 MessageType::NonConfirmable
150 },
151 code: rep.code,
152 message_id: mid,
153 token: observer.token.clone(),
154 options: Vec::new(),
155 payload: Vec::new(),
156 };
157 if keeps {
158 let seq = lock(&self.observers).next_sequence();
159 message
160 .options
161 .push((codec::OPT_OBSERVE, codec::uint_option(seq)));
162 }
163 let key = ResourceKey {
164 peer: observer.peer,
165 path: observer.path.clone(),
166 query: observer.query.clone(),
167 };
168 let szx = observer.block_szx.unwrap_or(codec::MAX_SZX);
169 self.fill_representation(&mut message, &rep, key, None, szx);
170
171 {
172 let mut obs = lock(&self.observers);
173 obs.record(observer.peer, mid, &observer.token, confirmable && keeps);
175 if !keeps {
176 obs.deregister(observer.peer, &observer.token);
177 report.ended += 1;
178 }
179 }
180 info!(
181 "CoAP notification {} {} to {} (mid {mid}) decision={}",
182 codec::code_to_string(rep.code),
183 path,
184 observer.peer,
185 if keeps {
186 Decision::Notified.as_str()
187 } else {
188 Decision::ObservationEnded.as_str()
189 }
190 );
191 let sent = self
192 .send(&message, observer.peer, observer.connection_id, app_state)
193 .await;
194 if confirmable && keeps {
195 if sent {
196 self.retransmit_later(app_state, observer, message).await;
197 } else {
198 lock(&self.observers).settle(observer.peer, mid);
199 }
200 }
201 report.sent += usize::from(sent);
202 }
203 report
204 }
205
206 async fn retransmit_later(
209 &self,
210 app_state: &AppState,
211 observer: Observer,
212 message: CoapMessage,
213 ) {
214 let Some(handle) = app_state
215 .server_handle::<CoapServerHandle>(self.server_id)
216 .await
217 else {
218 lock(&self.observers).settle(observer.peer, message.message_id);
219 return;
220 };
221 let state = app_state.clone();
222 let mut interval = self.ack_timeout.mul_f64(1.0 + rand::random::<f64>() * 0.5);
223 app_state
224 .spawn_server_task(self.server_id, async move {
225 for attempt in 0..=NOTIFICATION_MAX_RETRANSMIT {
226 tokio::time::sleep(interval).await;
227 if !lock(&handle.observers).awaiting_ack(observer.peer, message.message_id) {
231 return;
232 }
233 if attempt == NOTIFICATION_MAX_RETRANSMIT {
234 break;
235 }
236 debug!(
237 "CoAP notification mid {} to {} unacknowledged; retransmission {}",
238 message.message_id,
239 observer.peer,
240 attempt + 1
241 );
242 handle
243 .send(&message, observer.peer, observer.connection_id, &state)
244 .await;
245 interval *= 2;
246 }
247 {
248 let mut obs = lock(&handle.observers);
249 obs.settle(observer.peer, message.message_id);
250 obs.deregister(observer.peer, &observer.token);
251 }
252 warn!(
253 "CoAP observer {} of {} never acknowledged notification mid {} after {} \
254 retransmissions; observation removed decision={}",
255 observer.peer,
256 observer.path,
257 message.message_id,
258 NOTIFICATION_MAX_RETRANSMIT,
259 Decision::ObserverTimeout.as_str()
260 );
261 })
262 .await;
263 }
264
265 fn fill_representation(
272 &self,
273 message: &mut CoapMessage,
274 rep: &Arc<Representation>,
275 key: ResourceKey,
276 wanted: Option<BlockValue>,
277 szx: u8,
278 ) -> bool {
279 let size = codec::szx_size(szx);
280 if wanted.is_none() && rep.payload.len() <= size {
281 if let Some(cf) = rep.content_format {
282 message
283 .options
284 .push((codec::OPT_CONTENT_FORMAT, codec::uint_option(u32::from(cf))));
285 }
286 message.payload = rep.payload.clone();
287 return true;
288 }
289 let block = BlockValue {
290 num: wanted.map_or(0, |b| b.num),
291 more: false,
292 szx,
293 };
294 let Some((slice, more)) = rep.block(block) else {
295 return false;
296 };
297 message.options.push((codec::OPT_ETAG, rep.etag.clone()));
298 if let Some(cf) = rep.content_format {
299 message
300 .options
301 .push((codec::OPT_CONTENT_FORMAT, codec::uint_option(u32::from(cf))));
302 }
303 message
304 .options
305 .push((codec::OPT_BLOCK2, BlockValue { more, ..block }.encode()));
306 if block.num == 0 {
307 message.options.push((
308 codec::OPT_SIZE2,
309 codec::uint_option(rep.payload.len() as u32),
310 ));
311 }
312 message.payload = slice.to_vec();
313 let mut transfers = lock(&self.transfers);
314 if more {
315 if let Some(evicted) = transfers.insert(key, rep.clone(), slice.len(), Instant::now()) {
318 debug!(
319 "CoAP Block2 cache full; evicted {}{} for {}",
320 evicted.path,
321 evicted.query.map(|q| format!("?{q}")).unwrap_or_default(),
322 evicted.peer
323 );
324 }
325 } else {
326 transfers.remove(&key);
327 }
328 true
329 }
330
331 async fn send(
334 &self,
335 message: &CoapMessage,
336 peer_addr: SocketAddr,
337 connection_id: ConnectionId,
338 app_state: &AppState,
339 ) -> bool {
340 let bytes = match message.encode() {
344 Ok(bytes) => bytes,
345 Err(e) => {
346 error!(
347 "CoAP decision=fail_closed_encode: refusing to send {} to {}: {}",
348 codec::code_to_string(message.code),
349 peer_addr,
350 e
351 );
352 let _ = self.status_tx.send(format!(
353 "✗ CoAP refused to encode a reply to {peer_addr}: {e}"
354 ));
355 return false;
356 }
357 };
358 if let Err(e) = self.socket.send_to(&bytes, peer_addr).await {
359 error!("CoAP send to {} failed: {}", peer_addr, e);
360 let _ = self
361 .status_tx
362 .send(format!("✗ CoAP send to {peer_addr} failed: {e}"));
363 return false;
364 }
365
366 app_state
367 .update_connection_stats(
368 self.server_id,
369 connection_id,
370 None,
371 Some(bytes.len() as u64),
372 None,
373 Some(1),
374 )
375 .await;
376
377 let log = Log::new(Some(&self.status_tx));
378 log.debug(format!(
379 "CoAP sent {} {} ({} bytes) to {peer_addr}",
380 message.mtype.as_str(),
381 codec::code_to_string(message.code),
382 bytes.len()
383 ));
384 log.trace(format!("CoAP sent (hex): {}", hex::encode(&bytes)));
385 let _ = self
386 .status_tx
387 .send(format!("→ CoAP response to {peer_addr}"));
388 true
389 }
390}
391
392fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
395 m.lock().unwrap_or_else(|p| p.into_inner())
396}
397
398impl CoapServer {
399 pub async fn spawn_with_llm_actions(
404 listen_addr: SocketAddr,
405 llm_client: OllamaClient,
406 app_state: Arc<AppState>,
407 status_tx: mpsc::UnboundedSender<String>,
408 server_id: crate::state::ServerId,
409 notification_ack_timeout: Duration,
410 ) -> Result<SocketAddr> {
411 let socket = Arc::new(UdpSocket::bind(listen_addr).await?);
412 let local_addr = socket.local_addr()?;
413 Log::new(Some(&status_tx)).info(format!("CoAP server listening on {local_addr}"));
414
415 let protocol = Arc::new(CoapProtocol::new());
416 let handle = Arc::new(CoapServerHandle {
417 socket: socket.clone(),
418 server_id,
419 status_tx: status_tx.clone(),
420 next_message_id: AtomicU16::new(rand::random::<u16>()),
421 observers: Mutex::new(Observers::default()),
422 transfers: Mutex::new(Transfers::default()),
423 uploads: Mutex::new(Uploads::default()),
424 ack_timeout: notification_ack_timeout,
425 });
426 app_state
427 .register_server_handle(server_id, handle.clone())
428 .await;
429
430 let task_registrar = app_state.clone();
431 let recv_handle = tokio::spawn(async move {
432 Log::new(Some(&status_tx)).info(format!("CoAP receive loop started on {local_addr}"));
433
434 let mut buffer = vec![0u8; 2048];
441
442 loop {
443 match socket.recv_from(&mut buffer).await {
444 Ok((n, peer_addr)) => {
445 let data = buffer[..n].to_vec();
446 let connection_id =
447 ConnectionId::new(app_state.get_next_unified_id().await);
448
449 use crate::state::server::{
450 ConnectionState as ServerConnectionState, ConnectionStatus,
451 ProtocolConnectionInfo,
452 };
453 let now = crate::utils::clock::Instant::now();
454 app_state
455 .add_connection_to_server(
456 server_id,
457 ServerConnectionState {
458 id: connection_id,
459 remote_addr: peer_addr,
460 local_addr,
461 bytes_sent: 0,
462 bytes_received: n as u64,
463 packets_sent: 0,
464 packets_received: 1,
465 last_activity: now,
466 status: ConnectionStatus::Active,
467 status_changed_at: now,
468 protocol_info: ProtocolConnectionInfo::empty(),
469 },
470 )
471 .await;
472 let _ = status_tx.send("__UPDATE_UI__".to_string());
473
474 console_debug!(status_tx, "CoAP received {} bytes from {}", n, peer_addr);
475 console_trace!(status_tx, "CoAP received (hex): {}", hex::encode(&data));
476
477 let llm = llm_client.clone();
478 let st = app_state.clone();
479 let proto = protocol.clone();
480 let shared = handle.clone();
481
482 let task_owner = app_state.clone();
484 task_owner
485 .spawn_server_task(server_id, async move {
486 Self::handle_datagram(
487 data,
488 peer_addr,
489 connection_id,
490 llm,
491 st,
492 shared,
493 proto,
494 )
495 .await;
496 })
497 .await;
498 }
499 Err(e) => {
500 error!("CoAP receive error: {}", e);
501 break;
502 }
503 }
504 }
505 });
506
507 task_registrar
508 .register_server_task(server_id, recv_handle)
509 .await;
510
511 Ok(local_addr)
512 }
513
514 async fn handle_datagram(
515 data: Vec<u8>,
516 peer_addr: SocketAddr,
517 connection_id: ConnectionId,
518 llm_client: OllamaClient,
519 app_state: Arc<AppState>,
520 shared: Arc<CoapServerHandle>,
521 protocol: Arc<CoapProtocol>,
522 ) {
523 let server_id = shared.server_id;
524 let status_tx = shared.status_tx.clone();
525 let send = |message: CoapMessage| {
526 let shared = shared.clone();
527 let app_state = app_state.clone();
528 async move {
529 shared
530 .send(&message, peer_addr, connection_id, &app_state)
531 .await;
532 }
533 };
534
535 if data.len() > codec::MAX_MESSAGE_LEN {
543 warn!(
544 "CoAP refusing a {}-byte datagram from {} (limit {}) decision={}",
545 data.len(),
546 peer_addr,
547 codec::MAX_MESSAGE_LEN,
548 Decision::RefusedTooLarge.as_str()
549 );
550 let _ = status_tx.send(format!(
551 "✗ CoAP refused a {}-byte datagram from {peer_addr}: over the {}-byte limit",
552 data.len(),
553 codec::MAX_MESSAGE_LEN
554 ));
555
556 if let Some(prefix) = codec::message_prefix(&data) {
561 let reply = if codec::method_name(prefix.code).is_some() {
562 let fresh = shared.fresh_message_id();
563 Some(codec::response_to_prefix(
564 &prefix,
565 fresh,
566 codec::CODE_REQUEST_ENTITY_TOO_LARGE,
567 ))
568 } else if prefix.mtype == MessageType::Confirmable {
569 Some(codec::reset_for(prefix.message_id))
572 } else {
573 None
574 };
575 if let Some(reply) = reply {
576 send(reply).await;
577 }
578 }
579 return;
580 }
581
582 let request = match CoapMessage::decode(&data) {
583 Ok(m) => m,
584 Err(e) => {
585 warn!("CoAP malformed message from {}: {}", peer_addr, e);
586 let _ = status_tx.send(format!("✗ CoAP malformed message from {peer_addr}: {e}"));
587
588 if data.len() >= codec::HEADER_LEN
592 && MessageType::from_bits(data[0] >> 4) == MessageType::Confirmable
593 {
594 let message_id = u16::from_be_bytes([data[2], data[3]]);
595 send(codec::reset_for(message_id)).await;
596 }
597 return;
598 }
599 };
600
601 if request.is_empty_message() {
604 match request.mtype {
605 MessageType::Confirmable => {
608 debug!(
609 "CoAP ping from {}; replying RST decision={}",
610 peer_addr,
611 Decision::SpecReply.as_str()
612 );
613 send(codec::reset_for(request.message_id)).await;
614 }
615 MessageType::Acknowledgement => {
617 if lock(&shared.observers).acknowledge(
618 peer_addr,
619 request.message_id,
620 Instant::now(),
621 ) {
622 debug!(
623 "CoAP notification mid {} acknowledged by {}",
624 request.message_id, peer_addr
625 );
626 }
627 }
628 MessageType::Reset => {
631 let removed = lock(&shared.observers).reset(peer_addr, request.message_id);
632 match removed {
633 Some(observer) => info!(
634 "CoAP observer {} of {} answered notification mid {} with RST; \
635 observation removed decision={}",
636 peer_addr,
637 observer.path,
638 request.message_id,
639 Decision::ObserverReset.as_str()
640 ),
641 None => debug!(
642 "CoAP RST from {} for mid {} matches no notification",
643 peer_addr, request.message_id
644 ),
645 }
646 }
647 MessageType::NonConfirmable => {
648 debug!("CoAP empty NON message from {} ignored", peer_addr);
649 }
650 }
651 return;
652 }
653
654 if !request.is_request() {
655 if codec::code_class(request.code) == 0 {
658 warn!(
659 "CoAP unrecognised method {} from {}; replying 4.05 decision={}",
660 codec::code_to_string(request.code),
661 peer_addr,
662 Decision::SpecReply.as_str()
663 );
664 let fresh = shared.fresh_message_id();
665 send(codec::response_to(
666 &request,
667 fresh,
668 codec::CODE_METHOD_NOT_ALLOWED,
669 ))
670 .await;
671 } else {
672 debug!(
673 "CoAP {} message with response code {} from {} ignored; this is a server",
674 request.mtype.as_str(),
675 codec::code_to_string(request.code),
676 peer_addr
677 );
678 }
679 return;
680 }
681
682 let method = codec::method_name(request.code).unwrap_or("GET");
683 let path = request.uri_path();
684 let resource = ResourceKey {
685 peer: peer_addr,
686 path: path.clone(),
687 query: request.uri_query(),
688 };
689
690 let spec_reply = |code: u8, decision: &'static str, why: String| {
692 let fresh = shared.fresh_message_id();
693 let response = codec::response_to(&request, fresh, code);
694 info!(
695 "CoAP {method} {path} from {peer_addr}: {} {why} decision={decision}",
696 codec::code_to_string(code)
697 );
698 response
699 };
700
701 let block2 = match request.block2() {
704 None => None,
705 Some(Some(b)) => Some(b),
706 Some(None) => {
707 let reply = spec_reply(
708 codec::CODE_BAD_OPTION,
709 Decision::SpecReply.as_str(),
710 "unreadable Block2 option".into(),
711 );
712 send(reply).await;
713 return;
714 }
715 };
716
717 let mut payload = request.payload.clone();
720 let mut upload: Option<(u32, BlockValue)> = None;
721 match request.block1() {
722 None => {}
723 Some(None) => {
724 let reply = spec_reply(
725 codec::CODE_BAD_OPTION,
726 Decision::SpecReply.as_str(),
727 "unreadable Block1 option".into(),
728 );
729 send(reply).await;
730 return;
731 }
732 Some(Some(block)) => {
733 let key = UploadKey {
734 resource: resource.clone(),
735 method: request.code,
736 };
737 let size1 = request.option_uint(codec::OPT_SIZE1);
738 let step = lock(&shared.uploads).accept(
739 key,
740 block,
741 &request.payload,
742 size1,
743 Instant::now(),
744 );
745 match step {
746 UploadStep::Continue(echo) => {
747 let mut reply = spec_reply(
748 codec::CODE_CONTINUE,
749 Decision::UploadContinue.as_str(),
750 format!("block {} received", echo.num),
751 );
752 reply.options.push((codec::OPT_BLOCK1, echo.encode()));
753 send(reply).await;
754 return;
755 }
756 UploadStep::Refuse {
757 code,
758 size1,
759 decision,
760 reason,
761 } => {
762 let mut reply = spec_reply(code, decision, reason);
763 if let Some(max) = size1 {
764 reply
765 .options
766 .push((codec::OPT_SIZE1, codec::uint_option(max as u32)));
767 }
768 send(reply).await;
769 return;
770 }
771 UploadStep::Complete { body, blocks, last } => {
772 payload = body;
773 upload = Some((blocks, last));
774 }
775 }
776 }
777 }
778
779 if let Some(block) = block2.filter(|b| b.num > 0) {
785 let cached = lock(&shared.transfers).get(&resource, block, Instant::now());
786 if let Some(rep) = cached {
787 let fresh = shared.fresh_message_id();
788 let mut response = codec::response_to(&request, fresh, rep.code);
789 if shared.fill_representation(&mut response, &rep, resource, Some(block), block.szx)
790 {
791 debug!(
792 "CoAP {method} {path} block {} from {peer_addr} served from cache \
793 decision={}",
794 block.num,
795 Decision::BlockCache.as_str()
796 );
797 send(response).await;
798 } else {
799 let reply = spec_reply(
800 codec::CODE_BAD_OPTION,
801 Decision::SpecReply.as_str(),
802 format!("block {} is past the end of the representation", block.num),
803 );
804 send(reply).await;
805 }
806 return;
807 }
808 }
809
810 let observe = match (request.code, request.observe()) {
813 (codec::CODE_GET, Some(0)) if block2.is_none_or(|b| b.num == 0) => {
814 Some(ObserveRequest::Register)
815 }
816 (codec::CODE_GET, Some(1)) => {
817 if lock(&shared.observers).deregister(peer_addr, &request.token) {
818 info!(
819 "CoAP {peer_addr} deregistered from {path} decision={}",
820 Decision::ObservationEnded.as_str()
821 );
822 }
823 Some(ObserveRequest::Deregister)
824 }
825 _ => None,
826 };
827
828 let answer_with = actions::answer_with_for_request(method, &path);
831
832 let mut event_data = serde_json::json!({
833 "method": method,
834 "path": path,
835 "path_segments": request.path_segments(),
836 "message_type": request.mtype.as_str(),
837 "message_id": request.message_id,
838 "answer_with": answer_with,
839 });
840 if let Some(query) = request.uri_query() {
841 event_data["query"] = serde_json::json!(query);
842 }
843 if let Some(observe) = observe {
844 event_data["observe"] = serde_json::json!(observe.as_str());
845 }
846 if let Some((blocks, _)) = upload {
847 event_data["blocks"] = serde_json::json!(blocks);
848 }
849 if let Some(cf) = request.option_uint(codec::OPT_CONTENT_FORMAT) {
850 let id = cf as u16;
851 event_data["content_format"] = serde_json::json!(
852 codec::content_format_name(id).unwrap_or("application/octet-stream")
853 );
854 }
855 if let Some(accept) = request.option_uint(codec::OPT_ACCEPT) {
856 let id = accept as u16;
857 event_data["accept"] = serde_json::json!(
858 codec::content_format_name(id).unwrap_or("application/octet-stream")
859 );
860 }
861 if !payload.is_empty() {
862 event_data["payload_size"] = serde_json::json!(payload.len());
863 let printable = payload
864 .iter()
865 .all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace());
866 if printable {
867 event_data["payload"] =
868 serde_json::json!(String::from_utf8_lossy(&payload).to_string());
869 event_data["payload_encoding"] = serde_json::json!("utf8");
870 } else {
871 event_data["payload"] = serde_json::json!(hex::encode(&payload));
872 event_data["payload_encoding"] = serde_json::json!("hex");
873 }
874 }
875
876 debug!(
877 "CoAP {} {} {} from {} (mid={})",
878 request.mtype.as_str(),
879 method,
880 path,
881 peer_addr,
882 request.message_id
883 );
884
885 let event = Event::new(&COAP_REQUEST_EVENT, event_data);
886
887 let (decision, outcome) = match call_llm(
888 &llm_client,
889 &app_state,
890 server_id,
891 Some(connection_id),
892 &event,
893 protocol.as_ref(),
894 )
895 .await
896 {
897 Ok(execution_result) => {
898 for msg in execution_result.messages {
899 let _ = status_tx.send(msg);
900 }
901 Self::outcome_from_results(&request, &execution_result.protocol_results)
902 }
903 Err(e) => {
904 error!("CoAP LLM error for {} {}: {}", method, path, e);
906 let _ = status_tx.send(format!("✗ CoAP LLM error: {e}"));
907 (
910 Decision::FailClosedLlmError,
911 Outcome::Response {
912 code: codec::CODE_SERVICE_UNAVAILABLE,
913 payload: Vec::new(),
914 content_format: None,
915 observable: false,
916 },
917 )
918 }
919 };
920
921 let summary = format!(
925 "CoAP {method} {path} from {peer_addr} decision={}",
926 decision.as_str()
927 );
928 if decision.is_fail_closed() {
929 Log::new(Some(&status_tx)).error(format!(
930 "{summary} (answered 5.03 because no usable answer was produced)"
931 ));
932 } else {
933 debug!("{summary}");
934 }
935
936 let fresh = shared.fresh_message_id();
937 let message = match outcome {
938 Outcome::Ignore => {
939 debug!(
940 "CoAP deliberately sending no reply to {} {} from {}",
941 method, path, peer_addr
942 );
943 return;
944 }
945 Outcome::Reset => codec::reset_for(request.message_id),
946 Outcome::Response {
947 code,
948 payload,
949 content_format,
950 observable,
951 } => {
952 let mut response = codec::response_to(&request, fresh, code);
953 let rep = Arc::new(Representation::new(code, payload, content_format));
954
955 if observe == Some(ObserveRequest::Register) && codec::code_class(code) == 2 {
957 if observable {
958 let observer = Observer {
959 peer: peer_addr,
960 token: request.token.clone(),
961 path: path.clone(),
962 query: request.uri_query(),
963 block_szx: block2.map(|b| b.szx),
964 connection_id,
965 };
966 shared.expire_observers();
967 let mut observers = lock(&shared.observers);
968 match observers.register(observer, Instant::now()) {
969 Registered::Added | Registered::Replaced => {
970 let seq = observers.next_sequence();
971 drop(observers);
972 response
973 .options
974 .push((codec::OPT_OBSERVE, codec::uint_option(seq)));
975 info!(
976 "CoAP {peer_addr} now observes {path} decision={}",
977 Decision::ObserveRegistered.as_str()
978 );
979 }
980 Registered::Full => {
981 drop(observers);
982 warn!(
983 "CoAP {peer_addr} asked to observe {path}, but {} observers \
984 are registered; served without Observe decision={}",
985 observe::MAX_OBSERVERS,
986 Decision::ObserveCapReached.as_str()
987 );
988 }
989 Registered::PeerFull => {
990 drop(observers);
991 warn!(
992 "CoAP {peer_addr} asked to observe {path}, but its IP holds \
993 {} observations; served without Observe decision={}",
994 observe::MAX_OBSERVERS_PER_IP,
995 Decision::ObservePeerCapReached.as_str()
996 );
997 }
998 }
999 } else {
1000 info!(
1001 "CoAP {peer_addr} asked to observe {path}; the model declined \
1002 decision={}",
1003 Decision::ObserveDeclined.as_str()
1004 );
1005 }
1006 }
1007
1008 let szx = block2.map_or(codec::MAX_SZX, |b| b.szx);
1009 if !shared.fill_representation(&mut response, &rep, resource, block2, szx) {
1010 let reply = spec_reply(
1011 codec::CODE_BAD_OPTION,
1012 Decision::SpecReply.as_str(),
1013 format!(
1014 "block {} is past the end of the representation",
1015 block2.map_or(0, |b| b.num)
1016 ),
1017 );
1018 send(reply).await;
1019 return;
1020 }
1021 if let Some((_, last)) = upload {
1022 response.options.push((codec::OPT_BLOCK1, last.encode()));
1023 }
1024 response
1025 }
1026 };
1027
1028 send(message).await;
1029 }
1030
1031 fn outcome_from_results(
1036 request: &CoapMessage,
1037 results: &[ActionResult],
1038 ) -> (Decision, Outcome) {
1039 for result in results {
1040 let ActionResult::Custom { name, data } = result else {
1041 continue;
1042 };
1043 match name.as_str() {
1044 RESULT_RESPONSE => {
1045 let code = data
1046 .get("code")
1047 .and_then(|v| v.as_u64())
1048 .unwrap_or(codec::CODE_INTERNAL_SERVER_ERROR as u64)
1049 as u8;
1050 let payload = data
1051 .get("payload_hex")
1052 .and_then(|v| v.as_str())
1053 .and_then(|s| hex::decode(s).ok())
1054 .unwrap_or_default();
1055 let content_format = data
1056 .get("content_format")
1057 .and_then(|v| v.as_u64())
1058 .map(|v| v as u16);
1059 let observable = data
1062 .get("observable")
1063 .and_then(|v| v.as_bool())
1064 .unwrap_or(false);
1065 let decision = if codec::code_class(code) == 2 {
1068 Decision::ModelAnswer
1069 } else {
1070 Decision::ModelReject
1071 };
1072 return (
1073 decision,
1074 Outcome::Response {
1075 code,
1076 payload,
1077 content_format,
1078 observable,
1079 },
1080 );
1081 }
1082 RESULT_RESET => return (Decision::ModelReset, Outcome::Reset),
1083 RESULT_IGNORE => return (Decision::ModelSilent, Outcome::Ignore),
1084 _ => {}
1085 }
1086 }
1087
1088 error!(
1089 "CoAP: no usable action returned for {} {}; answering 5.03 Service Unavailable",
1090 codec::method_name(request.code).unwrap_or("?"),
1091 request.uri_path()
1092 );
1093 (
1094 Decision::FailClosedNoAction,
1095 Outcome::Response {
1096 code: codec::CODE_SERVICE_UNAVAILABLE,
1097 payload: Vec::new(),
1098 content_format: None,
1099 observable: false,
1100 },
1101 )
1102 }
1103}
1104
1105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1113enum Decision {
1114 ModelAnswer,
1116 ModelReject,
1118 ModelReset,
1120 ModelSilent,
1122 SpecReply,
1124 RefusedTooLarge,
1128 FailClosedLlmError,
1130 FailClosedNoAction,
1132 UploadContinue,
1134 BlockCache,
1136 ObserveRegistered,
1138 ObserveDeclined,
1140 ObserveCapReached,
1142 ObservePeerCapReached,
1145 Notified,
1147 ObservationEnded,
1149 ObserverReset,
1151 ObserverTimeout,
1153 ObserverExpired,
1155}
1156
1157impl Decision {
1158 fn as_str(self) -> &'static str {
1159 match self {
1160 Decision::ModelAnswer => "model_answer",
1161 Decision::ModelReject => "model_reject",
1162 Decision::ModelReset => "model_reset",
1163 Decision::ModelSilent => "model_silent",
1164 Decision::SpecReply => "spec_reply",
1165 Decision::RefusedTooLarge => "refused_too_large",
1166 Decision::FailClosedLlmError => "fail_closed_llm_error",
1167 Decision::FailClosedNoAction => "fail_closed_no_action",
1168 Decision::UploadContinue => "upload_continue",
1169 Decision::BlockCache => "block_cache",
1170 Decision::ObserveRegistered => "observe_registered",
1171 Decision::ObserveDeclined => "observe_declined",
1172 Decision::ObserveCapReached => "observe_cap_reached",
1173 Decision::ObservePeerCapReached => "observe_peer_cap_reached",
1174 Decision::Notified => "notified",
1175 Decision::ObservationEnded => "observation_ended",
1176 Decision::ObserverReset => "observer_reset",
1177 Decision::ObserverTimeout => "observer_timeout",
1178 Decision::ObserverExpired => "observer_expired",
1179 }
1180 }
1181
1182 fn is_fail_closed(self) -> bool {
1183 matches!(
1184 self,
1185 Decision::FailClosedLlmError | Decision::FailClosedNoAction
1186 )
1187 }
1188}
1189
1190enum Outcome {
1192 Response {
1193 code: u8,
1194 payload: Vec<u8>,
1195 content_format: Option<u16>,
1196 observable: bool,
1198 },
1199 Reset,
1200 Ignore,
1201}
1202
1203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1205enum ObserveRequest {
1206 Register,
1207 Deregister,
1208}
1209
1210impl ObserveRequest {
1211 fn as_str(self) -> &'static str {
1212 match self {
1213 ObserveRequest::Register => "register",
1214 ObserveRequest::Deregister => "deregister",
1215 }
1216 }
1217}