1use crate::block::{BlockAssembler, BlockDisassembler, szx_from_block_size};
36use crate::dtls::{DtlsConnection, DtlsContext, DtlsSessionCache};
37use crate::{CoapError, CoapResult};
38use std::collections::HashMap;
39use std::net::SocketAddr;
40use std::sync::Arc;
41use std::time::{Duration, Instant};
42use tokio::net::UdpSocket;
43use tokio::sync::Mutex;
44use tracing::{debug, info, warn};
45
46pub const COAP_VERSION: u8 = 1;
50
51const PAYLOAD_MARKER: u8 = 0xFF;
55
56pub const OPTION_URI_HOST: u16 = 3;
60
61pub const OPTION_URI_PORT: u16 = 7;
63
64pub const OPTION_URI_PATH: u16 = 11;
69
70pub const OPTION_CONTENT_FORMAT: u16 = 12;
74
75pub const OPTION_URI_QUERY: u16 = 15;
77
78pub const OPTION_BLOCK2: u16 = 23;
82
83pub const OPTION_BLOCK1: u16 = 27;
87
88pub const OPTION_SIZE2: u16 = 28;
92
93pub const OPTION_SIZE1: u16 = 60;
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum CoapMethod {
101 Get,
103 Post,
105 Put,
107 Delete,
109 Fetch,
111 Patch,
113 IPatch,
115}
116
117impl CoapMethod {
118 pub fn from_code(code: &CoapCode) -> Option<Self> {
122 if code.class != 0 {
123 return None;
124 }
125 match code.detail {
126 1 => Some(Self::Get),
127 2 => Some(Self::Post),
128 3 => Some(Self::Put),
129 4 => Some(Self::Delete),
130 5 => Some(Self::Fetch),
131 6 => Some(Self::Patch),
132 7 => Some(Self::IPatch),
133 _ => None,
134 }
135 }
136
137 pub fn to_code(&self) -> CoapCode {
139 let detail = match self {
140 Self::Get => 1,
141 Self::Post => 2,
142 Self::Put => 3,
143 Self::Delete => 4,
144 Self::Fetch => 5,
145 Self::Patch => 6,
146 Self::IPatch => 7,
147 };
148 CoapCode { class: 0, detail }
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
154pub enum CoapMessageType {
155 Confirmable,
157 NonConfirmable,
159 Acknowledgement,
161 Reset,
163}
164
165impl CoapMessageType {
166 fn from_bits(bits: u8) -> CoapResult<Self> {
168 match bits {
169 0 => Ok(Self::Confirmable),
170 1 => Ok(Self::NonConfirmable),
171 2 => Ok(Self::Acknowledgement),
172 3 => Ok(Self::Reset),
173 _ => Err(CoapError::InvalidMessage(format!(
174 "Invalid message type: {bits}"
175 ))),
176 }
177 }
178
179 fn to_bits(self) -> u8 {
181 match self {
182 Self::Confirmable => 0,
183 Self::NonConfirmable => 1,
184 Self::Acknowledgement => 2,
185 Self::Reset => 3,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
195pub struct CoapCode {
196 pub class: u8,
198 pub detail: u8,
200}
201
202impl CoapCode {
203 pub const BAD_REQUEST: Self = Self {
205 class: 4,
206 detail: 0,
207 };
208 pub const CREATED: Self = Self {
210 class: 2,
211 detail: 1,
212 };
213 pub const CONTENT: Self = Self {
215 class: 2,
216 detail: 5,
217 };
218 pub const UNAUTHORIZED: Self = Self {
220 class: 4,
221 detail: 1,
222 };
223 pub const FORBIDDEN: Self = Self {
225 class: 4,
226 detail: 3,
227 };
228 pub const NOT_FOUND: Self = Self {
230 class: 4,
231 detail: 4,
232 };
233 pub const METHOD_NOT_ALLOWED: Self = Self {
235 class: 4,
236 detail: 5,
237 };
238 pub const UNSUPPORTED_CONTENT_FORMAT: Self = Self {
240 class: 4,
241 detail: 15,
242 };
243 pub const INTERNAL_SERVER_ERROR: Self = Self {
245 class: 5,
246 detail: 0,
247 };
248
249 pub fn to_byte(&self) -> u8 {
251 ((self.class & 0x07) << 5) | (self.detail & 0x1F)
252 }
253
254 pub fn from_byte(byte: u8) -> Self {
256 Self {
257 class: (byte >> 5) & 0x07,
258 detail: byte & 0x1F,
259 }
260 }
261
262 pub fn is_request(&self) -> bool {
264 self.class == 0
265 }
266
267 pub fn is_success(&self) -> bool {
269 self.class == 2
270 }
271
272 pub fn is_client_error(&self) -> bool {
274 self.class == 4
275 }
276
277 pub fn is_server_error(&self) -> bool {
279 self.class == 5
280 }
281}
282
283impl std::fmt::Display for CoapCode {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 write!(f, "{}.{:02}", self.class, self.detail)
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
294pub struct CoapOption {
295 pub number: u16,
297 pub value: Vec<u8>,
299}
300
301impl CoapOption {
302 pub fn new(number: u16, value: Vec<u8>) -> Self {
304 Self { number, value }
305 }
306
307 pub fn empty(number: u16) -> Self {
309 Self {
310 number,
311 value: Vec::new(),
312 }
313 }
314
315 pub fn value_as_uint(&self) -> u32 {
320 let mut result: u32 = 0;
321 for &byte in &self.value {
322 result = (result << 8) | u32::from(byte);
323 }
324 result
325 }
326
327 pub fn from_uint(number: u16, value: u32) -> Self {
329 let bytes = if value == 0 {
330 Vec::new()
331 } else if value <= 0xFF {
332 vec![value as u8]
333 } else if value <= 0xFFFF {
334 vec![(value >> 8) as u8, value as u8]
335 } else if value <= 0xFF_FFFF {
336 vec![(value >> 16) as u8, (value >> 8) as u8, value as u8]
337 } else {
338 vec![
339 (value >> 24) as u8,
340 (value >> 16) as u8,
341 (value >> 8) as u8,
342 value as u8,
343 ]
344 };
345 Self {
346 number,
347 value: bytes,
348 }
349 }
350
351 pub fn value_as_str(&self) -> CoapResult<&str> {
353 std::str::from_utf8(&self.value)
354 .map_err(|e| CoapError::InvalidMessage(format!("Invalid UTF-8 in option: {e}")))
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Eq)]
363pub struct CoapMessage {
364 pub version: u8,
366 pub msg_type: CoapMessageType,
368 pub code: CoapCode,
370 pub message_id: u16,
372 pub token: Vec<u8>,
374 pub options: Vec<CoapOption>,
376 pub payload: Vec<u8>,
378}
379
380impl CoapMessage {
381 pub fn parse(data: &[u8]) -> CoapResult<Self> {
387 if data.len() < 4 {
388 return Err(CoapError::InvalidMessage(format!(
389 "Message too short: {} bytes (minimum 4)",
390 data.len()
391 )));
392 }
393
394 let version = (data[0] >> 6) & 0x03;
396 if version != COAP_VERSION {
397 return Err(CoapError::InvalidMessage(format!(
398 "Unsupported CoAP version: {version}"
399 )));
400 }
401
402 let msg_type = CoapMessageType::from_bits((data[0] >> 4) & 0x03)?;
403 let tkl = (data[0] & 0x0F) as usize;
404
405 if tkl > 8 {
406 return Err(CoapError::InvalidMessage(format!(
407 "Token length {tkl} exceeds maximum of 8"
408 )));
409 }
410
411 let code = CoapCode::from_byte(data[1]);
413
414 let message_id = u16::from_be_bytes([data[2], data[3]]);
416
417 let token_end = 4 + tkl;
419 if data.len() < token_end {
420 return Err(CoapError::InvalidMessage(format!(
421 "Message truncated in token: need {} bytes, have {}",
422 token_end,
423 data.len()
424 )));
425 }
426 let token = data[4..token_end].to_vec();
427
428 let mut pos = token_end;
430 let mut options = Vec::new();
431 let mut current_option_number: u16 = 0;
432
433 while pos < data.len() {
434 if data[pos] == PAYLOAD_MARKER {
436 pos += 1;
437 break;
438 }
439
440 let option_byte = data[pos];
442 pos += 1;
443
444 let mut delta = u16::from((option_byte >> 4) & 0x0F);
445 let mut length = u16::from(option_byte & 0x0F);
446
447 match delta {
449 13 => {
450 if pos >= data.len() {
451 return Err(CoapError::InvalidMessage(
452 "Truncated option delta (13)".to_string(),
453 ));
454 }
455 delta = u16::from(data[pos]) + 13;
456 pos += 1;
457 }
458 14 => {
459 if pos + 1 >= data.len() {
460 return Err(CoapError::InvalidMessage(
461 "Truncated option delta (14)".to_string(),
462 ));
463 }
464 delta = u16::from_be_bytes([data[pos], data[pos + 1]])
465 .checked_add(269)
466 .ok_or_else(|| CoapError::InvalidMessage("option value overflow".into()))?;
467 pos += 2;
468 }
469 15 => {
470 return Err(CoapError::InvalidMessage(
471 "Reserved option delta value 15".to_string(),
472 ));
473 }
474 _ => {}
475 }
476
477 match length {
479 13 => {
480 if pos >= data.len() {
481 return Err(CoapError::InvalidMessage(
482 "Truncated option length (13)".to_string(),
483 ));
484 }
485 length = u16::from(data[pos]) + 13;
486 pos += 1;
487 }
488 14 => {
489 if pos + 1 >= data.len() {
490 return Err(CoapError::InvalidMessage(
491 "Truncated option length (14)".to_string(),
492 ));
493 }
494 length = u16::from_be_bytes([data[pos], data[pos + 1]])
495 .checked_add(269)
496 .ok_or_else(|| CoapError::InvalidMessage("option value overflow".into()))?;
497 pos += 2;
498 }
499 15 => {
500 return Err(CoapError::InvalidMessage(
501 "Reserved option length value 15".to_string(),
502 ));
503 }
504 _ => {}
505 }
506
507 current_option_number = current_option_number
508 .checked_add(delta)
509 .ok_or_else(|| CoapError::InvalidMessage("option number overflow".into()))?;
510 let length = length as usize;
511
512 if pos + length > data.len() {
513 return Err(CoapError::InvalidMessage(format!(
514 "Truncated option value: need {} bytes at offset {}, have {}",
515 length,
516 pos,
517 data.len() - pos
518 )));
519 }
520
521 let value = data[pos..pos + length].to_vec();
522 pos += length;
523
524 options.push(CoapOption {
525 number: current_option_number,
526 value,
527 });
528 }
529
530 let payload = if pos < data.len() {
532 data[pos..].to_vec()
533 } else {
534 Vec::new()
535 };
536
537 Ok(Self {
538 version,
539 msg_type,
540 code,
541 message_id,
542 token,
543 options,
544 payload,
545 })
546 }
547
548 pub fn encode(&self) -> Vec<u8> {
553 let mut buf = Vec::with_capacity(4 + self.token.len() + self.payload.len() + 32);
554
555 let tkl = self.token.len().min(8) as u8;
557 buf.push((COAP_VERSION << 6) | (self.msg_type.to_bits() << 4) | tkl);
558
559 buf.push(self.code.to_byte());
561
562 buf.extend_from_slice(&self.message_id.to_be_bytes());
564
565 buf.extend_from_slice(&self.token[..tkl as usize]);
567
568 let mut sorted_options = self.options.clone();
570 sorted_options.sort_by_key(|o| o.number);
571
572 let mut prev_number: u16 = 0;
573 for opt in &sorted_options {
574 let delta = opt.number - prev_number;
575 let length = opt.value.len() as u16;
576 prev_number = opt.number;
577
578 let (delta_nibble, delta_ext) = encode_option_header_value(delta);
580 let (length_nibble, length_ext) = encode_option_header_value(length);
582
583 buf.push((delta_nibble << 4) | length_nibble);
584 buf.extend_from_slice(&delta_ext);
585 buf.extend_from_slice(&length_ext);
586 buf.extend_from_slice(&opt.value);
587 }
588
589 if !self.payload.is_empty() {
591 buf.push(PAYLOAD_MARKER);
592 buf.extend_from_slice(&self.payload);
593 }
594
595 buf
596 }
597
598 pub fn uri_path(&self) -> String {
603 let segments: Vec<&str> = self
604 .options
605 .iter()
606 .filter(|o| o.number == OPTION_URI_PATH)
607 .filter_map(|o| std::str::from_utf8(&o.value).ok())
608 .collect();
609
610 if segments.is_empty() {
611 "/".to_string()
612 } else {
613 format!("/{}", segments.join("/"))
614 }
615 }
616
617 pub fn content_format(&self) -> Option<u16> {
619 self.options
620 .iter()
621 .find(|o| o.number == OPTION_CONTENT_FORMAT)
622 .map(|o| o.value_as_uint() as u16)
623 }
624
625 pub fn block1(&self) -> Option<crate::block::BlockOption> {
627 self.options
628 .iter()
629 .find(|o| o.number == OPTION_BLOCK1)
630 .map(|o| crate::block::BlockOption::decode(o.value_as_uint()))
631 }
632
633 pub fn block2(&self) -> Option<crate::block::BlockOption> {
635 self.options
636 .iter()
637 .find(|o| o.number == OPTION_BLOCK2)
638 .map(|o| crate::block::BlockOption::decode(o.value_as_uint()))
639 }
640}
641
642fn encode_option_header_value(value: u16) -> (u8, Vec<u8>) {
645 if value < 13 {
646 (value as u8, Vec::new())
647 } else if value < 269 {
648 (13, vec![(value - 13) as u8])
649 } else {
650 let extended = value - 269;
651 (14, extended.to_be_bytes().to_vec())
652 }
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
660pub enum EstOperation {
661 CaCerts,
663 SimpleEnroll,
665 SimpleReenroll,
667 ServerKeygen,
669 CsrAttrs,
671}
672
673#[derive(Debug)]
685pub struct CoapEstRequest {
686 pub operation: EstOperation,
688 pub label: Option<String>,
696 pub method: CoapMethod,
698 pub message: CoapMessage,
700}
701
702pub struct CoapEstRouter;
710
711impl CoapEstRouter {
712 pub fn route(path: &str) -> CoapResult<EstOperation> {
727 Self::route_with_label(path).map(|(_, op)| op)
728 }
729
730 pub fn route_with_label(path: &str) -> CoapResult<(Option<String>, EstOperation)> {
746 let remainder = path
750 .trim_start_matches('/')
751 .trim_start_matches(".well-known/est/")
752 .trim_start_matches(".well-known/est")
753 .trim_matches('/');
754
755 let segments: Vec<&str> = remainder.split('/').filter(|s| !s.is_empty()).collect();
756
757 let (label, op_segment) = match segments.as_slice() {
758 [op] => (None, *op),
759 [label, op] => (Some((*label).to_string()), *op),
760 _ => {
761 return Err(CoapError::ResourceNotFound(format!(
762 "Unknown EST-coaps path: {path}"
763 )));
764 }
765 };
766
767 let operation = match op_segment {
768 "sen" | "simpleenroll" => EstOperation::SimpleEnroll,
769 "sren" | "simplereenroll" => EstOperation::SimpleReenroll,
770 "skg" | "serverkeygen" => EstOperation::ServerKeygen,
771 "att" | "csrattrs" => EstOperation::CsrAttrs,
772 "cacerts" | "crts" => EstOperation::CaCerts,
773 _ => {
774 return Err(CoapError::ResourceNotFound(format!(
775 "Unknown EST-coaps path: {path}"
776 )));
777 }
778 };
779
780 Ok((label, operation))
781 }
782
783 pub fn route_message(message: CoapMessage) -> CoapResult<CoapEstRequest> {
789 let path = message.uri_path();
790 let (label, operation) = Self::route_with_label(&path)?;
791
792 let method = CoapMethod::from_code(&message.code).ok_or_else(|| {
793 CoapError::UnsupportedMethod(format!("Code {} is not a request", message.code))
794 })?;
795
796 match operation {
798 EstOperation::CaCerts | EstOperation::CsrAttrs => {
799 if method != CoapMethod::Get && method != CoapMethod::Fetch {
800 return Err(CoapError::UnsupportedMethod(format!(
801 "{path} requires GET or FETCH, got {method:?}"
802 )));
803 }
804 }
805 EstOperation::SimpleEnroll
806 | EstOperation::SimpleReenroll
807 | EstOperation::ServerKeygen => {
808 if method != CoapMethod::Post {
809 return Err(CoapError::UnsupportedMethod(format!(
810 "{path} requires POST, got {method:?}"
811 )));
812 }
813 }
814 }
815
816 Ok(CoapEstRequest {
817 operation,
818 label,
819 method,
820 message,
821 })
822 }
823}
824
825#[cfg(test)]
826mod tests {
827 use super::*;
828
829 #[test]
832 fn test_code_byte_roundtrip() {
833 let codes = [
834 CoapCode::CREATED,
835 CoapCode::CONTENT,
836 CoapCode::NOT_FOUND,
837 CoapCode::METHOD_NOT_ALLOWED,
838 CoapCode::UNSUPPORTED_CONTENT_FORMAT,
839 CoapCode::INTERNAL_SERVER_ERROR,
840 ];
841
842 for code in codes {
843 let byte = code.to_byte();
844 let decoded = CoapCode::from_byte(byte);
845 assert_eq!(decoded, code, "roundtrip failed for {code}");
846 }
847 }
848
849 #[test]
850 fn test_code_classification() {
851 assert!(CoapCode::from_byte(0x01).is_request()); assert!(CoapCode::CONTENT.is_success());
853 assert!(CoapCode::NOT_FOUND.is_client_error());
854 assert!(CoapCode::INTERNAL_SERVER_ERROR.is_server_error());
855 }
856
857 #[test]
858 fn test_code_display() {
859 assert_eq!(CoapCode::CONTENT.to_string(), "2.05");
860 assert_eq!(CoapCode::NOT_FOUND.to_string(), "4.04");
861 assert_eq!(CoapCode::CREATED.to_string(), "2.01");
862 }
863
864 #[test]
867 fn test_option_uint_encoding() {
868 let opt = CoapOption::from_uint(OPTION_CONTENT_FORMAT, 285);
869 assert_eq!(opt.value_as_uint(), 285);
870
871 let opt_zero = CoapOption::from_uint(OPTION_CONTENT_FORMAT, 0);
872 assert_eq!(opt_zero.value_as_uint(), 0);
873 assert!(opt_zero.value.is_empty());
874 }
875
876 #[test]
877 fn test_option_string_value() {
878 let opt = CoapOption::new(OPTION_URI_PATH, b"sen".to_vec());
879 assert_eq!(opt.value_as_str().unwrap(), "sen");
880 }
881
882 #[test]
885 fn test_parse_minimal_message() {
886 let data = [0x40, 0x01, 0x12, 0x34];
889 let msg = CoapMessage::parse(&data).unwrap();
890
891 assert_eq!(msg.version, 1);
892 assert_eq!(msg.msg_type, CoapMessageType::Confirmable);
893 assert_eq!(
894 msg.code,
895 CoapCode {
896 class: 0,
897 detail: 1
898 }
899 );
900 assert_eq!(msg.message_id, 0x1234);
901 assert!(msg.token.is_empty());
902 assert!(msg.options.is_empty());
903 assert!(msg.payload.is_empty());
904 }
905
906 #[test]
907 fn test_parse_message_with_token() {
908 let data = [0x44, 0x01, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF];
910 let msg = CoapMessage::parse(&data).unwrap();
911
912 assert_eq!(msg.token, vec![0xDE, 0xAD, 0xBE, 0xEF]);
913 }
914
915 #[test]
916 fn test_parse_message_with_payload() {
917 let mut data = vec![0x40, 0x02, 0x00, 0x01];
919 data.push(PAYLOAD_MARKER);
920 data.extend_from_slice(b"hello");
921
922 let msg = CoapMessage::parse(&data).unwrap();
923 assert_eq!(msg.payload, b"hello");
924 }
925
926 #[test]
927 fn test_parse_encode_roundtrip() {
928 let original = CoapMessage {
929 version: 1,
930 msg_type: CoapMessageType::Confirmable,
931 code: CoapMethod::Post.to_code(),
932 message_id: 0xABCD,
933 token: vec![0x01, 0x02],
934 options: vec![
935 CoapOption::new(OPTION_URI_PATH, b".well-known".to_vec()),
936 CoapOption::new(OPTION_URI_PATH, b"est".to_vec()),
937 CoapOption::new(OPTION_URI_PATH, b"sen".to_vec()),
938 CoapOption::from_uint(OPTION_CONTENT_FORMAT, 285),
939 ],
940 payload: vec![0x30, 0x82, 0x01, 0x00],
941 };
942
943 let encoded = original.encode();
944 let decoded = CoapMessage::parse(&encoded).unwrap();
945
946 assert_eq!(decoded.version, original.version);
947 assert_eq!(decoded.msg_type, original.msg_type);
948 assert_eq!(decoded.code, original.code);
949 assert_eq!(decoded.message_id, original.message_id);
950 assert_eq!(decoded.token, original.token);
951 assert_eq!(decoded.payload, original.payload);
952 assert_eq!(decoded.options.len(), original.options.len());
953 }
954
955 #[test]
956 fn test_parse_too_short() {
957 let err = CoapMessage::parse(&[0x40, 0x01]).unwrap_err();
958 assert!(matches!(err, CoapError::InvalidMessage(_)));
959 }
960
961 #[test]
962 fn test_parse_bad_version() {
963 let data = [0x80, 0x01, 0x00, 0x01];
965 let err = CoapMessage::parse(&data).unwrap_err();
966 assert!(matches!(err, CoapError::InvalidMessage(_)));
967 }
968
969 #[test]
970 fn test_parse_tkl_too_large() {
971 let data = [0x49, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0];
973 let err = CoapMessage::parse(&data).unwrap_err();
974 assert!(matches!(err, CoapError::InvalidMessage(_)));
975 }
976
977 #[test]
980 fn test_uri_path_extraction() {
981 let msg = CoapMessage {
982 version: 1,
983 msg_type: CoapMessageType::Confirmable,
984 code: CoapMethod::Post.to_code(),
985 message_id: 1,
986 token: vec![],
987 options: vec![
988 CoapOption::new(OPTION_URI_PATH, b".well-known".to_vec()),
989 CoapOption::new(OPTION_URI_PATH, b"est".to_vec()),
990 CoapOption::new(OPTION_URI_PATH, b"sen".to_vec()),
991 ],
992 payload: vec![],
993 };
994
995 assert_eq!(msg.uri_path(), "/.well-known/est/sen");
996 }
997
998 #[test]
999 fn test_uri_path_empty() {
1000 let msg = CoapMessage {
1001 version: 1,
1002 msg_type: CoapMessageType::Confirmable,
1003 code: CoapMethod::Get.to_code(),
1004 message_id: 1,
1005 token: vec![],
1006 options: vec![],
1007 payload: vec![],
1008 };
1009
1010 assert_eq!(msg.uri_path(), "/");
1011 }
1012
1013 #[test]
1016 fn test_route_abbreviated_paths() {
1017 assert_eq!(
1018 CoapEstRouter::route("/sen").unwrap(),
1019 EstOperation::SimpleEnroll
1020 );
1021 assert_eq!(
1022 CoapEstRouter::route("/sren").unwrap(),
1023 EstOperation::SimpleReenroll
1024 );
1025 assert_eq!(
1026 CoapEstRouter::route("/skg").unwrap(),
1027 EstOperation::ServerKeygen
1028 );
1029 assert_eq!(
1030 CoapEstRouter::route("/att").unwrap(),
1031 EstOperation::CsrAttrs
1032 );
1033 assert_eq!(
1034 CoapEstRouter::route("/cacerts").unwrap(),
1035 EstOperation::CaCerts
1036 );
1037 assert_eq!(
1038 CoapEstRouter::route("/crts").unwrap(),
1039 EstOperation::CaCerts
1040 );
1041 }
1042
1043 #[test]
1044 fn test_route_well_known_prefix() {
1045 assert_eq!(
1046 CoapEstRouter::route("/.well-known/est/sen").unwrap(),
1047 EstOperation::SimpleEnroll
1048 );
1049 assert_eq!(
1050 CoapEstRouter::route("/.well-known/est/cacerts").unwrap(),
1051 EstOperation::CaCerts
1052 );
1053 }
1054
1055 #[test]
1056 fn test_route_full_names() {
1057 assert_eq!(
1058 CoapEstRouter::route("/simpleenroll").unwrap(),
1059 EstOperation::SimpleEnroll
1060 );
1061 assert_eq!(
1062 CoapEstRouter::route("/simplereenroll").unwrap(),
1063 EstOperation::SimpleReenroll
1064 );
1065 assert_eq!(
1066 CoapEstRouter::route("/serverkeygen").unwrap(),
1067 EstOperation::ServerKeygen
1068 );
1069 assert_eq!(
1070 CoapEstRouter::route("/csrattrs").unwrap(),
1071 EstOperation::CsrAttrs
1072 );
1073 }
1074
1075 #[test]
1076 fn test_route_unknown_path() {
1077 let err = CoapEstRouter::route("/unknown").unwrap_err();
1078 assert!(matches!(err, CoapError::ResourceNotFound(_)));
1079 }
1080
1081 #[test]
1082 fn test_route_with_label_unlabeled() {
1083 assert_eq!(
1085 CoapEstRouter::route_with_label("/.well-known/est/sen").unwrap(),
1086 (None, EstOperation::SimpleEnroll)
1087 );
1088 assert_eq!(
1089 CoapEstRouter::route_with_label("/sen").unwrap(),
1090 (None, EstOperation::SimpleEnroll)
1091 );
1092 }
1093
1094 #[test]
1095 fn test_route_with_label_labeled() {
1096 assert_eq!(
1098 CoapEstRouter::route_with_label("/.well-known/est/devices/sen").unwrap(),
1099 (Some("devices".to_string()), EstOperation::SimpleEnroll)
1100 );
1101 assert_eq!(
1102 CoapEstRouter::route_with_label("/.well-known/est/mylabel/simplereenroll").unwrap(),
1103 (Some("mylabel".to_string()), EstOperation::SimpleReenroll)
1104 );
1105 assert_eq!(
1106 CoapEstRouter::route_with_label("/mylabel/cacerts").unwrap(),
1107 (Some("mylabel".to_string()), EstOperation::CaCerts)
1108 );
1109 }
1110
1111 #[test]
1112 fn test_route_with_label_unknown_operation() {
1113 let err = CoapEstRouter::route_with_label("/.well-known/est/mylabel/bogus").unwrap_err();
1115 assert!(matches!(err, CoapError::ResourceNotFound(_)));
1116 }
1117
1118 #[test]
1119 fn test_route_with_label_too_many_segments() {
1120 let err = CoapEstRouter::route_with_label("/.well-known/est/a/b/sen").unwrap_err();
1122 assert!(matches!(err, CoapError::ResourceNotFound(_)));
1123 }
1124
1125 #[test]
1128 fn test_message_block1_option() {
1129 let block = crate::block::BlockOption {
1130 num: 3,
1131 more: true,
1132 szx: 5,
1133 };
1134
1135 let msg = CoapMessage {
1136 version: 1,
1137 msg_type: CoapMessageType::Confirmable,
1138 code: CoapMethod::Post.to_code(),
1139 message_id: 1,
1140 token: vec![],
1141 options: vec![CoapOption::from_uint(OPTION_BLOCK1, block.encode())],
1142 payload: vec![],
1143 };
1144
1145 let extracted = msg.block1().unwrap();
1146 assert_eq!(extracted, block);
1147 }
1148
1149 #[test]
1150 fn test_message_content_format() {
1151 let msg = CoapMessage {
1152 version: 1,
1153 msg_type: CoapMessageType::Confirmable,
1154 code: CoapMethod::Post.to_code(),
1155 message_id: 1,
1156 token: vec![],
1157 options: vec![CoapOption::from_uint(
1158 OPTION_CONTENT_FORMAT,
1159 u32::from(crate::content_format::APPLICATION_PKCS10),
1160 )],
1161 payload: vec![],
1162 };
1163
1164 assert_eq!(
1165 msg.content_format(),
1166 Some(crate::content_format::APPLICATION_PKCS10)
1167 );
1168 }
1169
1170 #[test]
1173 fn test_route_message_post_simpleenroll() {
1174 let msg = CoapMessage {
1175 version: 1,
1176 msg_type: CoapMessageType::Confirmable,
1177 code: CoapMethod::Post.to_code(),
1178 message_id: 42,
1179 token: vec![0x01],
1180 options: vec![
1181 CoapOption::new(OPTION_URI_PATH, b".well-known".to_vec()),
1182 CoapOption::new(OPTION_URI_PATH, b"est".to_vec()),
1183 CoapOption::new(OPTION_URI_PATH, b"sen".to_vec()),
1184 ],
1185 payload: vec![0x30],
1186 };
1187
1188 let req = CoapEstRouter::route_message(msg).unwrap();
1189 assert_eq!(req.operation, EstOperation::SimpleEnroll);
1190 assert_eq!(req.method, CoapMethod::Post);
1191 }
1192
1193 #[test]
1194 fn test_route_message_get_cacerts() {
1195 let msg = CoapMessage {
1196 version: 1,
1197 msg_type: CoapMessageType::Confirmable,
1198 code: CoapMethod::Get.to_code(),
1199 message_id: 43,
1200 token: vec![],
1201 options: vec![CoapOption::new(OPTION_URI_PATH, b"cacerts".to_vec())],
1202 payload: vec![],
1203 };
1204
1205 let req = CoapEstRouter::route_message(msg).unwrap();
1206 assert_eq!(req.operation, EstOperation::CaCerts);
1207 assert_eq!(req.method, CoapMethod::Get);
1208 }
1209
1210 #[test]
1211 fn test_route_message_wrong_method() {
1212 let msg = CoapMessage {
1213 version: 1,
1214 msg_type: CoapMessageType::Confirmable,
1215 code: CoapMethod::Get.to_code(), message_id: 44,
1217 token: vec![],
1218 options: vec![CoapOption::new(OPTION_URI_PATH, b"sen".to_vec())],
1219 payload: vec![],
1220 };
1221
1222 let err = CoapEstRouter::route_message(msg).unwrap_err();
1223 assert!(matches!(err, CoapError::UnsupportedMethod(_)));
1224 }
1225
1226 #[test]
1229 fn test_encode_parse_roundtrip_with_large_option_delta() {
1230 let msg = CoapMessage {
1231 version: 1,
1232 msg_type: CoapMessageType::Acknowledgement,
1233 code: CoapCode::CONTENT,
1234 message_id: 999,
1235 token: vec![0xAA, 0xBB, 0xCC],
1236 options: vec![
1237 CoapOption::new(OPTION_URI_PATH, b"test".to_vec()), CoapOption::from_uint(OPTION_CONTENT_FORMAT, 285), CoapOption::from_uint(OPTION_SIZE1, 4096), ],
1241 payload: b"response-body".to_vec(),
1242 };
1243
1244 let bytes = msg.encode();
1245 let parsed = CoapMessage::parse(&bytes).unwrap();
1246
1247 assert_eq!(parsed.version, 1);
1248 assert_eq!(parsed.msg_type, CoapMessageType::Acknowledgement);
1249 assert_eq!(parsed.code, CoapCode::CONTENT);
1250 assert_eq!(parsed.message_id, 999);
1251 assert_eq!(parsed.token, vec![0xAA, 0xBB, 0xCC]);
1252 assert_eq!(parsed.payload, b"response-body");
1253
1254 assert_eq!(parsed.content_format(), Some(285));
1256 let size1 = parsed
1257 .options
1258 .iter()
1259 .find(|o| o.number == OPTION_SIZE1)
1260 .unwrap();
1261 assert_eq!(size1.value_as_uint(), 4096);
1262 }
1263}
1264
1265#[derive(Debug, Clone)]
1276pub struct AuditInfo {
1277 pub event_type: String,
1279 pub detail: String,
1281}
1282
1283#[derive(Debug, Clone)]
1288pub struct EstResponse {
1289 pub payload: Vec<u8>,
1291 pub content_format: u16,
1293 pub audit_event: Option<AuditInfo>,
1295}
1296
1297pub trait EstHandler: Send + Sync + 'static {
1308 fn handle(
1323 &self,
1324 operation: EstOperation,
1325 label: Option<&str>,
1326 payload: &[u8],
1327 content_format: Option<u16>,
1328 client_cert: Option<&crate::dtls::ClientCertInfo>,
1329 ) -> Result<EstResponse, CoapError>;
1330}
1331
1332type TransferKey = (SocketAddr, Vec<u8>, String, u8);
1334type ExchangeEntry = (Vec<u8>, Option<Vec<u8>>, Instant);
1335type ResponseCacheEntry = (BlockDisassembler, u16, CoapCode, Instant);
1336
1337pub struct CoapDtlsServer {
1357 socket: Arc<UdpSocket>,
1359 dtls_ctx: DtlsContext,
1361 session_cache: Mutex<DtlsSessionCache>,
1363 connections: Mutex<HashMap<SocketAddr, DtlsConnection>>,
1365 block_assemblers: Mutex<HashMap<TransferKey, (BlockAssembler, Instant)>>,
1375 response_cache: Mutex<HashMap<TransferKey, ResponseCacheEntry>>,
1381 block_szx: u8,
1383 exchanges: Mutex<HashMap<(SocketAddr, u16), ExchangeEntry>>,
1384 max_payload: usize,
1386 max_block_assemblers: usize,
1388 block_ttl: Duration,
1390}
1391
1392impl CoapDtlsServer {
1393 pub async fn bind(
1406 listen_addr: &str,
1407 cert_pem: &[u8],
1408 key_pem: &[u8],
1409 ca_pem: &[u8],
1410 block_size: u16,
1411 max_payload: usize,
1412 max_sessions: usize,
1413 session_timeout: Duration,
1414 ) -> Result<Self, CoapError> {
1415 let socket = UdpSocket::bind(listen_addr).await.map_err(|e| {
1416 CoapError::Internal(format!("Failed to bind UDP socket on {listen_addr}: {e}"))
1417 })?;
1418
1419 let dtls_ctx = DtlsContext::new(cert_pem, key_pem, ca_pem)?;
1420
1421 let block_szx =
1422 szx_from_block_size(block_size as usize).unwrap_or(crate::block::DEFAULT_SZX);
1423
1424 info!(
1425 listen_addr = %listen_addr,
1426 block_size = block_size,
1427 max_payload = max_payload,
1428 max_sessions = max_sessions,
1429 "CoAP/DTLS server bound"
1430 );
1431
1432 Ok(Self {
1433 socket: Arc::new(socket),
1434 dtls_ctx,
1435 session_cache: Mutex::new(DtlsSessionCache::new(max_sessions, session_timeout)),
1436 connections: Mutex::new(HashMap::new()),
1437 block_assemblers: Mutex::new(HashMap::new()),
1438 response_cache: Mutex::new(HashMap::new()),
1439 block_szx,
1440 exchanges: Mutex::new(HashMap::new()),
1441 max_payload,
1442 max_block_assemblers: max_sessions.saturating_mul(2),
1446 block_ttl: Duration::from_secs(60),
1447 })
1448 }
1449
1450 pub fn local_addr(&self) -> Result<SocketAddr, CoapError> {
1452 self.socket
1453 .local_addr()
1454 .map_err(|e| CoapError::Internal(format!("Failed to get local address: {e}")))
1455 }
1456
1457 pub async fn run<H: EstHandler>(&self, handler: Arc<H>) -> Result<(), CoapError> {
1468 let mut buf = vec![0u8; 65535];
1469
1470 info!("CoAP/DTLS server main loop started");
1471
1472 let mut maintenance = tokio::time::interval(Duration::from_millis(100));
1473 maintenance.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
1474 loop {
1475 let received = tokio::select! {
1476 biased;
1478 _ = maintenance.tick() => {
1479 let packets = {
1480 let mut conns = self.connections.lock().await;
1481 let mut packets = Vec::new();
1482 conns.retain(|peer, conn| {
1483 if !conn.is_handshake_complete()
1484 && conn.created_at.elapsed() > Duration::from_secs(10)
1485 {
1486 return false;
1487 }
1488 if conn.handle_timeout().is_err() {
1489 return false;
1490 }
1491 packets.extend(conn.outgoing().into_iter().map(|packet| (*peer, packet)));
1492 true
1493 });
1494 packets
1495 };
1496 for (peer, packet) in packets {
1497 let _ = self.socket.send_to(&packet, peer).await;
1498 }
1499 continue;
1500 }
1501 result = self.socket.recv_from(&mut buf) => result,
1502 };
1503 let (len, peer_addr) = match received {
1504 Ok(result) => result,
1505 Err(e) => {
1506 warn!(error = %e, "UDP recv_from failed, continuing");
1507 continue;
1508 }
1509 };
1510
1511 let datagram = &buf[..len];
1512 debug!(
1513 peer = %peer_addr,
1514 len = len,
1515 "Received UDP datagram"
1516 );
1517
1518 let incoming = {
1519 let mut conns = self.connections.lock().await;
1520 let cache = self.session_cache.lock().await;
1521 conns.retain(|_, c| {
1522 c.created_at.elapsed()
1523 < if c.is_handshake_complete() {
1524 cache.ttl()
1525 } else {
1526 Duration::from_secs(10)
1527 }
1528 });
1529 if !conns.contains_key(&peer_addr) {
1530 if conns.len() >= cache.max_sessions() {
1531 continue;
1532 }
1533 self.block_assemblers
1534 .lock()
1535 .await
1536 .retain(|key, _| key.0 != peer_addr);
1537 self.response_cache
1538 .lock()
1539 .await
1540 .retain(|key, _| key.0 != peer_addr);
1541 self.exchanges
1542 .lock()
1543 .await
1544 .retain(|key, _| key.0 != peer_addr);
1545 conns.insert(peer_addr, DtlsConnection::new(&self.dtls_ctx, peer_addr)?);
1546 }
1547 let conn = conns.get_mut(&peer_addr).unwrap();
1548 match conn.receive(datagram) {
1549 Ok(messages) => Some((messages, conn.outgoing())),
1550 Err(e) => {
1551 debug!(error = %e, "DTLS datagram rejected");
1552 conns.remove(&peer_addr);
1553 None
1554 }
1555 }
1556 };
1557 if let Some((messages, outgoing)) = incoming {
1558 for packet in outgoing {
1559 self.socket
1560 .send_to(&packet, peer_addr)
1561 .await
1562 .map_err(|e| CoapError::Internal(e.to_string()))?;
1563 }
1564 for message in messages {
1565 if let Err(e) = self
1566 .process_datagram(&message, peer_addr, handler.clone())
1567 .await
1568 {
1569 warn!(error = %e, "CoAP request failed");
1570 }
1571 }
1572 }
1573 }
1574 }
1575
1576 async fn reap_stale_entries(&self) {
1581 let deadline = Instant::now() - self.block_ttl;
1582
1583 {
1584 let mut assemblers = self.block_assemblers.lock().await;
1585 let before = assemblers.len();
1586 assemblers.retain(|_key, (_asm, ts)| *ts > deadline);
1587 let reaped = before - assemblers.len();
1588 if reaped > 0 {
1589 debug!(reaped = reaped, "Reaped stale block assemblers");
1590 }
1591 }
1592
1593 {
1594 let mut cache = self.response_cache.lock().await;
1595 let before = cache.len();
1596 cache.retain(|_key, (_disasm, _cf, _code, ts)| *ts > deadline);
1597 let reaped = before - cache.len();
1598 if reaped > 0 {
1599 debug!(reaped = reaped, "Reaped stale response cache entries");
1600 }
1601 }
1602 }
1603
1604 async fn process_datagram(
1606 &self,
1607 data: &[u8],
1608 peer_addr: SocketAddr,
1609 handler: Arc<dyn EstHandler>,
1610 ) -> Result<(), CoapError> {
1611 self.reap_stale_entries().await;
1613
1614 let message = CoapMessage::parse(data)?;
1615
1616 if message.msg_type != CoapMessageType::Confirmable
1617 && message.msg_type != CoapMessageType::NonConfirmable
1618 {
1619 return Ok(());
1620 }
1621 if message.payload.len() > self.max_payload {
1622 return self
1623 .send_response(
1624 peer_addr,
1625 &self.build_error_response(
1626 &message.token,
1627 message.message_id,
1628 &CoapError::PayloadTooLarge {
1629 size: message.payload.len(),
1630 max: self.max_payload,
1631 },
1632 ),
1633 )
1634 .await;
1635 }
1636 {
1637 let mut exchanges = self.exchanges.lock().await;
1638 exchanges.retain(|_, (_, _, time)| time.elapsed() < Duration::from_secs(247));
1639 if let Some((request, response, _)) = exchanges.get(&(peer_addr, message.message_id)) {
1640 if request != data {
1641 return Err(CoapError::InvalidMessage(
1642 "message ID reused during exchange lifetime".into(),
1643 ));
1644 }
1645 let response = response.clone();
1646 drop(exchanges);
1647 if let Some(response) = response {
1648 self.send_encoded(peer_addr, &response).await?;
1649 }
1650 return Ok(());
1651 }
1652 if exchanges.len() >= self.max_block_assemblers.saturating_mul(16) {
1653 return Err(CoapError::Internal("exchange capacity exhausted".into()));
1654 }
1655 exchanges.insert(
1656 (peer_addr, message.message_id),
1657 (data.to_vec(), None, Instant::now()),
1658 );
1659 }
1660
1661 debug!(
1662 peer = %peer_addr,
1663 msg_type = ?message.msg_type,
1664 code = %message.code,
1665 path = %message.uri_path(),
1666 "Parsed CoAP message"
1667 );
1668
1669 if !message.code.is_request() {
1671 debug!(peer = %peer_addr, "Ignoring non-request message");
1672 return Ok(());
1673 }
1674
1675 if let Some(block2) = message.block2()
1681 && block2.num > 0
1682 {
1683 let cache_key = (
1684 peer_addr,
1685 message.token.clone(),
1686 message.uri_path(),
1687 message.code.to_byte(),
1688 );
1689 let mut cache = self.response_cache.lock().await;
1690 if let Some((disasm, cf, code, ts)) = cache.get_mut(&cache_key) {
1691 *ts = Instant::now();
1693 if let Some((block_data, block_opt)) = disasm.get_block(block2.num) {
1694 let is_last = !block_opt.more;
1695 let response = CoapMessage {
1696 version: COAP_VERSION,
1697 msg_type: CoapMessageType::Acknowledgement,
1698 code: *code,
1699 message_id: message.message_id,
1700 token: message.token.clone(),
1701 options: vec![
1702 CoapOption::from_uint(OPTION_CONTENT_FORMAT, u32::from(*cf)),
1703 CoapOption::from_uint(OPTION_BLOCK2, block_opt.encode()),
1704 CoapOption::from_uint(OPTION_SIZE2, disasm.payload_len() as u32),
1705 ],
1706 payload: block_data,
1707 };
1708 if is_last {
1710 cache.remove(&cache_key);
1711 }
1712 drop(cache);
1713 self.send_response(peer_addr, &response).await?;
1714 return Ok(());
1715 } else {
1716 cache.remove(&cache_key);
1718 }
1719 }
1720 drop(cache);
1721 return self
1722 .send_response(
1723 peer_addr,
1724 &self.build_error_response(
1725 &message.token,
1726 message.message_id,
1727 &CoapError::InvalidMessage("unknown Block2 exchange".into()),
1728 ),
1729 )
1730 .await;
1731 }
1732
1733 let block1 = message.block1();
1735 let content_format = message.content_format();
1736 let msg_token = message.token.clone();
1737 let msg_id = message.message_id;
1738
1739 let est_request = match CoapEstRouter::route_message(message) {
1741 Ok(req) => req,
1742 Err(e) => {
1743 let response = self.build_error_response(&msg_token, msg_id, &e);
1744 self.send_response(peer_addr, &response).await?;
1745 return Ok(());
1746 }
1747 };
1748
1749 let request_payload = if let Some(block) = block1 {
1751 let assembler_key = (
1752 peer_addr,
1753 msg_token.clone(),
1754 est_request.message.uri_path(),
1755 est_request.message.code.to_byte(),
1756 );
1757 let mut assemblers = self.block_assemblers.lock().await;
1758
1759 if !assemblers.contains_key(&assembler_key)
1763 && assemblers.len() >= self.max_block_assemblers
1764 {
1765 warn!(
1766 peer = %peer_addr,
1767 active = assemblers.len(),
1768 limit = self.max_block_assemblers,
1769 "Block assembler limit reached, rejecting new transfer"
1770 );
1771 return Err(CoapError::Internal(
1772 "too many concurrent block transfers".into(),
1773 ));
1774 }
1775
1776 let (assembler, ts) = assemblers
1777 .entry(assembler_key.clone())
1778 .or_insert_with(|| (BlockAssembler::new(self.max_payload), Instant::now()));
1779
1780 *ts = Instant::now();
1782
1783 let complete = assembler.process_block(&block, &est_request.message.payload)?;
1784
1785 if !complete {
1786 let ack = CoapMessage {
1788 version: COAP_VERSION,
1789 msg_type: CoapMessageType::Acknowledgement,
1790 code: CoapCode {
1791 class: 2,
1792 detail: 31,
1793 }, message_id: msg_id,
1795 token: msg_token.clone(),
1796 options: vec![CoapOption::from_uint(OPTION_BLOCK1, block.encode())],
1797 payload: Vec::new(),
1798 };
1799 self.send_response(peer_addr, &ack).await?;
1800 return Ok(());
1801 }
1802
1803 let (assembler, _ts) = assemblers.remove(&assembler_key).unwrap();
1805 assembler.into_payload()?
1806 } else {
1807 est_request.message.payload.clone()
1808 };
1809
1810 if request_payload.len() > self.max_payload {
1811 let e = CoapError::PayloadTooLarge {
1812 size: request_payload.len(),
1813 max: self.max_payload,
1814 };
1815 self.send_response(
1816 peer_addr,
1817 &self.build_error_response(&msg_token, msg_id, &e),
1818 )
1819 .await?;
1820 return Ok(());
1821 }
1822
1823 let client_cert = {
1825 let conns = self.connections.lock().await;
1826 conns.get(&peer_addr).and_then(|c| c.client_cert().cloned())
1827 };
1828
1829 let operation = est_request.operation;
1831 let label = est_request.label.clone();
1832 let est_response = match tokio::task::spawn_blocking(move || {
1833 handler.handle(
1834 operation,
1835 label.as_deref(),
1836 &request_payload,
1837 content_format,
1838 client_cert.as_ref(),
1839 )
1840 })
1841 .await
1842 .map_err(|e| CoapError::Internal(e.to_string()))?
1843 {
1844 Ok(result) => result,
1845 Err(e) => {
1846 let response = self.build_error_response(&msg_token, msg_id, &e);
1847 self.send_response(peer_addr, &response).await?;
1848 return Ok(());
1849 }
1850 };
1851
1852 let audit_event = est_response.audit_event.clone();
1854 let response_payload = est_response.payload;
1855 if response_payload.len() > self.max_payload {
1856 return Err(CoapError::PayloadTooLarge {
1857 size: response_payload.len(),
1858 max: self.max_payload,
1859 });
1860 }
1861 let response_cf = est_response.content_format;
1862
1863 let response_code = match est_request.operation {
1865 EstOperation::SimpleEnroll
1866 | EstOperation::SimpleReenroll
1867 | EstOperation::ServerKeygen => CoapCode::CREATED,
1868 EstOperation::CaCerts | EstOperation::CsrAttrs => CoapCode::CONTENT,
1869 };
1870
1871 let block_size = crate::block::block_size_from_szx(self.block_szx);
1873 if response_payload.len() > block_size {
1874 let disasm = BlockDisassembler::new(response_payload, self.block_szx);
1876 if let Some((block_data, block_opt)) = disasm.get_block(0) {
1877 let response = CoapMessage {
1878 version: COAP_VERSION,
1879 msg_type: CoapMessageType::Acknowledgement,
1880 code: response_code,
1881 message_id: msg_id,
1882 token: msg_token.clone(),
1883 options: vec![
1884 CoapOption::from_uint(OPTION_CONTENT_FORMAT, u32::from(response_cf)),
1885 CoapOption::from_uint(OPTION_BLOCK2, block_opt.encode()),
1886 CoapOption::from_uint(OPTION_SIZE2, disasm.payload_len() as u32),
1887 ],
1888 payload: block_data,
1889 };
1890 self.send_response(peer_addr, &response).await?;
1891 }
1892
1893 let cache_key = (
1895 peer_addr,
1896 msg_token.clone(),
1897 est_request.message.uri_path(),
1898 est_request.message.code.to_byte(),
1899 );
1900 let mut cache = self.response_cache.lock().await;
1901 if cache.len() < self.max_block_assemblers {
1903 cache.insert(
1904 cache_key,
1905 (disasm, response_cf, response_code, Instant::now()),
1906 );
1907 } else {
1908 warn!(
1909 peer = %peer_addr,
1910 active = cache.len(),
1911 limit = self.max_block_assemblers,
1912 "Response cache full, client must re-request"
1913 );
1914 }
1915 } else {
1916 let response = CoapMessage {
1918 version: COAP_VERSION,
1919 msg_type: CoapMessageType::Acknowledgement,
1920 code: response_code,
1921 message_id: msg_id,
1922 token: msg_token,
1923 options: vec![CoapOption::from_uint(
1924 OPTION_CONTENT_FORMAT,
1925 u32::from(response_cf),
1926 )],
1927 payload: response_payload,
1928 };
1929 self.send_response(peer_addr, &response).await?;
1930 }
1931
1932 if let Some(audit) = audit_event {
1934 info!(
1935 event_type = %audit.event_type,
1936 detail = %audit.detail,
1937 peer = %peer_addr,
1938 "CoAP audit event"
1939 );
1940 }
1944
1945 Ok(())
1946 }
1947
1948 fn build_error_response(
1950 &self,
1951 token: &[u8],
1952 message_id: u16,
1953 error: &CoapError,
1954 ) -> CoapMessage {
1955 let code = match error {
1956 CoapError::InvalidMessage(_) => CoapCode::BAD_REQUEST,
1957 CoapError::ResourceNotFound(_) => CoapCode::NOT_FOUND,
1958 CoapError::UnsupportedMethod(_) => CoapCode::METHOD_NOT_ALLOWED,
1959 CoapError::UnsupportedContentFormat(_) => CoapCode::UNSUPPORTED_CONTENT_FORMAT,
1960 CoapError::Unauthorized(_) => CoapCode::UNAUTHORIZED,
1961 CoapError::Forbidden(_) => CoapCode::FORBIDDEN,
1962 CoapError::PayloadTooLarge { .. } => CoapCode {
1963 class: 4,
1964 detail: 13, },
1966 _ => CoapCode::INTERNAL_SERVER_ERROR,
1967 };
1968
1969 CoapMessage {
1970 version: COAP_VERSION,
1971 msg_type: CoapMessageType::Acknowledgement,
1972 code,
1973 message_id,
1974 token: token.to_vec(),
1975 options: Vec::new(),
1976 payload: error.to_string().into_bytes(),
1977 }
1978 }
1979
1980 async fn send_response(
1984 &self,
1985 peer_addr: SocketAddr,
1986 message: &CoapMessage,
1987 ) -> Result<(), CoapError> {
1988 let encoded = message.encode();
1989 debug!(
1990 peer = %peer_addr,
1991 code = %message.code,
1992 len = encoded.len(),
1993 "Sending CoAP response"
1994 );
1995
1996 if let Some((_, response, _)) = self
1997 .exchanges
1998 .lock()
1999 .await
2000 .get_mut(&(peer_addr, message.message_id))
2001 {
2002 *response = Some(encoded.clone());
2003 }
2004 self.send_encoded(peer_addr, &encoded).await
2005 }
2006
2007 async fn send_encoded(&self, peer_addr: SocketAddr, encoded: &[u8]) -> Result<(), CoapError> {
2008 let outgoing = {
2009 let mut connections = self.connections.lock().await;
2010 let connection = connections
2011 .get_mut(&peer_addr)
2012 .ok_or_else(|| CoapError::Unauthorized("DTLS session required".into()))?;
2013 connection.encrypt(encoded)?;
2014 connection.outgoing()
2015 };
2016 for packet in outgoing {
2017 self.socket
2018 .send_to(&packet, peer_addr)
2019 .await
2020 .map_err(|e| CoapError::Internal(e.to_string()))?;
2021 }
2022
2023 Ok(())
2024 }
2025}
2026
2027#[cfg(test)]
2028mod server_tests {
2029 use super::*;
2030
2031 struct EchoHandler;
2033
2034 impl EstHandler for EchoHandler {
2035 fn handle(
2036 &self,
2037 operation: EstOperation,
2038 _label: Option<&str>,
2039 _payload: &[u8],
2040 _content_format: Option<u16>,
2041 _client_cert: Option<&crate::dtls::ClientCertInfo>,
2042 ) -> Result<EstResponse, CoapError> {
2043 let name = match operation {
2044 EstOperation::CaCerts => "cacerts",
2045 EstOperation::SimpleEnroll => "simpleenroll",
2046 EstOperation::SimpleReenroll => "simplereenroll",
2047 EstOperation::ServerKeygen => "serverkeygen",
2048 EstOperation::CsrAttrs => "csrattrs",
2049 };
2050 Ok(EstResponse {
2051 payload: name.as_bytes().to_vec(),
2052 content_format: crate::content_format::APPLICATION_PKCS7_MIME_CERTS_ONLY,
2053 audit_event: Some(AuditInfo {
2054 event_type: format!("coap_{name}"),
2055 detail: format!("test operation: {name}"),
2056 }),
2057 })
2058 }
2059 }
2060
2061 #[test]
2062 fn test_echo_handler() {
2063 let handler = EchoHandler;
2064 let resp = handler
2065 .handle(EstOperation::CaCerts, None, &[], None, None)
2066 .unwrap();
2067 assert_eq!(resp.payload, b"cacerts");
2068 assert_eq!(
2069 resp.content_format,
2070 crate::content_format::APPLICATION_PKCS7_MIME_CERTS_ONLY
2071 );
2072 let audit = resp.audit_event.unwrap();
2073 assert_eq!(audit.event_type, "coap_cacerts");
2074 }
2075
2076 #[test]
2077 fn test_build_error_response_not_found() {
2078 let err = CoapError::ResourceNotFound("test".to_string());
2081 let code = match &err {
2082 CoapError::InvalidMessage(_) => CoapCode::BAD_REQUEST,
2083 CoapError::ResourceNotFound(_) => CoapCode::NOT_FOUND,
2084 _ => CoapCode::INTERNAL_SERVER_ERROR,
2085 };
2086 assert_eq!(code, CoapCode::NOT_FOUND);
2087 }
2088
2089 #[test]
2090 fn test_build_error_response_method_not_allowed() {
2091 let err = CoapError::UnsupportedMethod("test".to_string());
2092 let code = match &err {
2093 CoapError::UnsupportedMethod(_) => CoapCode::METHOD_NOT_ALLOWED,
2094 _ => CoapCode::INTERNAL_SERVER_ERROR,
2095 };
2096 assert_eq!(code, CoapCode::METHOD_NOT_ALLOWED);
2097 }
2098
2099 #[tokio::test]
2100 async fn authenticated_dtls_dispatch_and_duplicate_replay() {
2101 let (cert, key) = generate_test_cert();
2102 let server = Arc::new(
2103 CoapDtlsServer::bind(
2104 "127.0.0.1:0",
2105 &cert,
2106 &key,
2107 &cert,
2108 64,
2109 1024,
2110 4,
2111 Duration::from_secs(60),
2112 )
2113 .await
2114 .unwrap(),
2115 );
2116 let address = server.local_addr().unwrap();
2117 struct Counting(std::sync::atomic::AtomicUsize);
2118 impl EstHandler for Counting {
2119 fn handle(
2120 &self,
2121 _: EstOperation,
2122 _: Option<&str>,
2123 payload: &[u8],
2124 _: Option<u16>,
2125 cert: Option<&crate::dtls::ClientCertInfo>,
2126 ) -> Result<EstResponse, CoapError> {
2127 assert!(cert.is_some());
2128 self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2129 Ok(EstResponse {
2130 payload: if payload == b"fragment" {
2131 vec![42; 128]
2132 } else {
2133 b"issued".to_vec()
2134 },
2135 content_format: 281,
2136 audit_event: None,
2137 })
2138 }
2139 }
2140 let handler = Arc::new(Counting(std::sync::atomic::AtomicUsize::new(0)));
2141 let running = {
2142 let server = server.clone();
2143 let handler = handler.clone();
2144 tokio::spawn(async move { server.run(handler).await })
2145 };
2146 let request = CoapMessage {
2147 version: 1,
2148 msg_type: CoapMessageType::Confirmable,
2149 code: CoapMethod::Post.to_code(),
2150 message_id: 4,
2151 token: vec![1],
2152 options: vec![CoapOption::new(OPTION_URI_PATH, b"sen".to_vec())],
2153 payload: vec![1],
2154 }
2155 .encode();
2156 let raw = UdpSocket::bind("127.0.0.1:0").await.unwrap();
2157 raw.send_to(&request, address).await.unwrap();
2158 tokio::time::sleep(Duration::from_millis(30)).await;
2159 assert_eq!(handler.0.load(std::sync::atomic::Ordering::SeqCst), 0);
2160 let noise = tokio::spawn(async move {
2163 let mut interval = tokio::time::interval(Duration::from_millis(5));
2164 loop {
2165 interval.tick().await;
2166 raw.send_to(b"unrelated invalid datagram", address)
2167 .await
2168 .unwrap();
2169 }
2170 });
2171 tokio::task::spawn_blocking(move || {
2172 struct Datagram(std::net::UdpSocket, bool);
2173 impl std::io::Read for Datagram {
2174 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2175 if self.1 {
2176 let _ = self.0.recv(buf)?;
2177 self.1 = false;
2178 }
2179 self.0.recv(buf)
2180 }
2181 }
2182 impl std::io::Write for Datagram {
2183 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2184 self.0.send(buf)
2185 }
2186 fn flush(&mut self) -> std::io::Result<()> {
2187 Ok(())
2188 }
2189 }
2190 let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
2191 socket.connect(address).unwrap();
2192 socket
2193 .set_read_timeout(Some(Duration::from_secs(3)))
2194 .unwrap();
2195 let mut ctx =
2196 openssl::ssl::SslContext::builder(openssl::ssl::SslMethod::dtls_client()).unwrap();
2197 let certificate = openssl::x509::X509::from_pem(&cert).unwrap();
2198 ctx.set_certificate(&certificate).unwrap();
2199 ctx.set_private_key(&openssl::pkey::PKey::private_key_from_pem(&key).unwrap())
2200 .unwrap();
2201 ctx.cert_store_mut().add_cert(certificate).unwrap();
2202 ctx.set_verify(openssl::ssl::SslVerifyMode::PEER);
2203 let mut ssl = openssl::ssl::Ssl::new(&ctx.build()).unwrap();
2204 ssl.set_mtu(1200).unwrap();
2205 let mut stream = openssl::ssl::SslStream::new(ssl, Datagram(socket, true)).unwrap();
2206 stream.connect().unwrap();
2207 let mut buf = [0; 1024];
2208 stream.ssl_write(&request).unwrap();
2209 let first = stream.ssl_read(&mut buf).unwrap();
2210 let response = buf[..first].to_vec();
2211 assert_eq!(CoapMessage::parse(&response).unwrap().payload, b"issued");
2212 stream.ssl_write(&request).unwrap();
2213 let second = stream.ssl_read(&mut buf).unwrap();
2214 assert_eq!(&buf[..second], &response);
2215 let mut message = CoapMessage::parse(&request).unwrap();
2216 message.message_id = 5;
2217 message.payload = vec![1; 1025];
2218 stream.ssl_write(&message.encode()).unwrap();
2219 let n = stream.ssl_read(&mut buf).unwrap();
2220 assert_eq!(
2221 CoapMessage::parse(&buf[..n]).unwrap().code,
2222 CoapCode {
2223 class: 4,
2224 detail: 13
2225 }
2226 );
2227 message.message_id = 6;
2228 message.token = vec![1, 0];
2229 message.payload = b"fragment".to_vec();
2230 stream.ssl_write(&message.encode()).unwrap();
2231 let n = stream.ssl_read(&mut buf).unwrap();
2232 assert_eq!(CoapMessage::parse(&buf[..n]).unwrap().payload, vec![42; 64]);
2233 message.message_id = 7;
2234 message.token = vec![0, 31];
2235 message.payload.clear();
2236 message.options.push(CoapOption::from_uint(
2237 OPTION_BLOCK2,
2238 crate::block::BlockOption {
2239 num: 1,
2240 more: false,
2241 szx: 2,
2242 }
2243 .encode(),
2244 ));
2245 stream.ssl_write(&message.encode()).unwrap();
2246 let n = stream.ssl_read(&mut buf).unwrap();
2247 assert_eq!(
2248 CoapMessage::parse(&buf[..n]).unwrap().code,
2249 CoapCode::BAD_REQUEST
2250 );
2251 message.message_id = 8;
2252 message.token = vec![1, 0];
2253 stream.ssl_write(&message.encode()).unwrap();
2254 let n = stream.ssl_read(&mut buf).unwrap();
2255 assert_eq!(CoapMessage::parse(&buf[..n]).unwrap().payload, vec![42; 64]);
2256 })
2257 .await
2258 .unwrap();
2259 assert_eq!(handler.0.load(std::sync::atomic::Ordering::SeqCst), 2);
2260 noise.abort();
2261 running.abort();
2262 }
2263
2264 fn generate_test_cert() -> (Vec<u8>, Vec<u8>) {
2266 use openssl::asn1::Asn1Time;
2267 use openssl::bn::BigNum;
2268 use openssl::hash::MessageDigest;
2269 use openssl::pkey::PKey;
2270 use openssl::rsa::Rsa;
2271 use openssl::x509::{X509Builder, X509NameBuilder};
2272
2273 let rsa = Rsa::generate(2048).unwrap();
2274 let key = PKey::from_rsa(rsa).unwrap();
2275
2276 let mut name_builder = X509NameBuilder::new().unwrap();
2277 name_builder
2278 .append_entry_by_text("CN", "test-coap-server")
2279 .unwrap();
2280 let name = name_builder.build();
2281
2282 let mut builder = X509Builder::new().unwrap();
2283 builder.set_version(2).unwrap();
2284 builder.set_subject_name(&name).unwrap();
2285 builder.set_issuer_name(&name).unwrap();
2286 builder.set_pubkey(&key).unwrap();
2287
2288 let serial = BigNum::from_u32(1).unwrap();
2289 builder
2290 .set_serial_number(&serial.to_asn1_integer().unwrap())
2291 .unwrap();
2292
2293 let not_before = Asn1Time::days_from_now(0).unwrap();
2294 let not_after = Asn1Time::days_from_now(365).unwrap();
2295 builder.set_not_before(¬_before).unwrap();
2296 builder.set_not_after(¬_after).unwrap();
2297
2298 builder.sign(&key, MessageDigest::sha256()).unwrap();
2299 let cert = builder.build();
2300
2301 (
2302 cert.to_pem().unwrap(),
2303 key.private_key_to_pem_pkcs8().unwrap(),
2304 )
2305 }
2306}