1use std::fmt::{Display, Formatter};
11use std::str::FromStr;
12
13use url::Url;
14
15use crate::{
16 error::{MessageConversionError, MessageTypeError, OptionValueError},
17 message::{CoapMessage, CoapMessageCommon, CoapOption},
18 protocol::{
19 CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionType, CoapRequestCode, ContentFormat, ETag, HopLimit,
20 NoResponse, Observe,
21 },
22 types::{CoapUri, CoapUriHost, CoapUriScheme},
23};
24
25pub const MAX_URI_SEGMENT_LENGTH: usize = 255;
26pub const MAX_PROXY_URI_LENGTH: usize = 1034;
27
28#[derive(Clone, Eq, PartialEq, Hash, Debug)]
30enum CoapRequestUri {
31 Request(CoapUri),
32 Proxy(CoapUri),
33}
34
35impl Display for CoapRequestUri {
36 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37 match self {
38 CoapRequestUri::Request(v) => f.write_fmt(format_args!("Request URI: {}", v)),
39 CoapRequestUri::Proxy(v) => f.write_fmt(format_args!("Proxy URI: {}", v)),
40 }
41 }
42}
43
44impl CoapRequestUri {
45 #[allow(clippy::or_fun_call)]
50 pub fn new_request_uri(uri: CoapUri) -> Result<CoapRequestUri, OptionValueError> {
51 if uri
52 .path_iter()
53 .unwrap_or(vec![].iter())
54 .chain(uri.query_iter().unwrap_or(vec![].iter()))
55 .any(|x| x.len() > MAX_URI_SEGMENT_LENGTH)
56 {
57 return Err(OptionValueError::TooLong);
58 }
59 Ok(CoapRequestUri::Request(uri))
60 }
61
62 pub fn new_proxy_uri(uri: CoapUri) -> Result<CoapRequestUri, OptionValueError> {
65 if uri.scheme().is_none() || uri.host().is_none() {
66 return Err(OptionValueError::IllegalValue);
67 }
68 if CoapRequestUri::generate_proxy_uri_string(&uri).len() > MAX_PROXY_URI_LENGTH {
69 return Err(OptionValueError::TooLong);
70 }
71 Ok(CoapRequestUri::Proxy(uri))
72 }
73
74 fn generate_proxy_uri_string(uri: &CoapUri) -> String {
76 let mut proxy_uri_string = format!(
77 "{}://{}",
78 uri.scheme().unwrap().to_string().as_str(),
79 uri.host().unwrap().to_string().as_str()
80 );
81 if let Some(port) = uri.port() {
82 proxy_uri_string.push_str(format!(":{}", port).as_str());
83 }
84 if let Some(path) = uri.path_iter() {
85 path.for_each(|path_component| {
86 proxy_uri_string.push_str(format!("/{}", path_component).as_str());
87 });
88 }
89 if let Some(query) = uri.query_iter() {
90 let mut separator_char = '?';
91 query.for_each(|query_option| {
92 proxy_uri_string.push_str(format!("{}{}", separator_char, query_option).as_str());
93 separator_char = '&';
94 });
95 }
96 proxy_uri_string
97 }
98
99 pub fn into_options(self) -> Vec<CoapOption> {
101 let mut options = Vec::new();
102 match self {
103 CoapRequestUri::Request(mut uri) => {
104 if let Some(host) = uri.host() {
105 options.push(CoapOption::UriHost(host.to_string()))
106 }
107 if let Some(port) = uri.port() {
108 options.push(CoapOption::UriPort(port))
109 }
110 if let Some(path) = uri.drain_path_iter() {
111 options.extend(path.map(CoapOption::UriPath))
112 }
113 if let Some(query) = uri.drain_query_iter() {
114 options.extend(query.map(CoapOption::UriQuery))
115 }
116 },
117 CoapRequestUri::Proxy(uri) => {
118 options.push(CoapOption::ProxyUri(CoapRequestUri::generate_proxy_uri_string(&uri)))
119 },
120 }
121 options
122 }
123
124 pub fn as_uri(&self) -> &CoapUri {
126 match self {
127 CoapRequestUri::Request(uri) => uri,
128 CoapRequestUri::Proxy(uri) => uri,
129 }
130 }
131}
132
133impl TryFrom<CoapUri> for CoapRequestUri {
134 type Error = OptionValueError;
135
136 fn try_from(value: CoapUri) -> Result<Self, Self::Error> {
137 CoapRequestUri::new_request_uri(value)
138 }
139}
140
141#[derive(Debug, Clone, Eq, PartialEq, Hash)]
146pub struct CoapRequest {
147 pdu: CoapMessage,
148 uri: Option<CoapRequestUri>,
149 accept: Option<ContentFormat>,
150 etag: Option<Vec<ETag>>,
151 if_match: Option<Vec<CoapMatch>>,
152 content_format: Option<ContentFormat>,
153 if_none_match: bool,
154 hop_limit: Option<HopLimit>,
155 no_response: Option<NoResponse>,
156 observe: Option<Observe>,
157}
158
159impl CoapRequest {
160 pub fn new(type_: CoapMessageType, code: CoapRequestCode) -> Result<CoapRequest, MessageTypeError> {
165 match type_ {
166 CoapMessageType::Con | CoapMessageType::Non => {},
167 v => return Err(MessageTypeError::InvalidForMessageCode(v)),
168 }
169 Ok(CoapRequest {
170 pdu: CoapMessage::new(type_, code.into()),
171 uri: None,
172 accept: None,
173 etag: None,
174 if_match: None,
175 content_format: None,
176 if_none_match: false,
177 hop_limit: None,
178 no_response: None,
179 observe: None,
180 })
181 }
182
183 pub fn accept(&self) -> Option<ContentFormat> {
185 self.accept
186 }
187
188 pub fn set_accept(&mut self, accept: Option<ContentFormat>) {
195 self.accept = accept
196 }
197
198 pub fn etag(&self) -> Option<&Vec<ETag>> {
200 self.etag.as_ref()
201 }
202
203 pub fn set_etag(&mut self, etag: Option<Vec<ETag>>) {
213 self.etag = etag
214 }
215
216 pub fn if_match(&self) -> Option<&Vec<CoapMatch>> {
218 self.if_match.as_ref()
219 }
220
221 pub fn set_if_match(&mut self, if_match: Option<Vec<CoapMatch>>) {
229 self.if_match = if_match
230 }
231
232 pub fn content_format(&self) -> Option<ContentFormat> {
234 self.content_format
235 }
236
237 pub fn set_content_format(&mut self, content_format: Option<ContentFormat>) {
246 self.content_format = content_format;
247 }
248
249 pub fn if_none_match(&self) -> bool {
251 self.if_none_match
252 }
253
254 pub fn set_if_none_match(&mut self, if_none_match: bool) {
264 self.if_none_match = if_none_match
265 }
266
267 pub fn hop_limit(&self) -> Option<HopLimit> {
269 self.hop_limit
270 }
271
272 pub fn set_hop_limit(&mut self, hop_limit: Option<HopLimit>) {
280 self.hop_limit = hop_limit;
281 }
282
283 pub fn no_response(&self) -> Option<NoResponse> {
285 self.no_response
286 }
287
288 pub fn set_no_response(&mut self, no_response: Option<NoResponse>) {
296 self.no_response = no_response;
297 }
298
299 pub fn observe(&self) -> Option<Observe> {
301 self.observe
302 }
303
304 pub fn set_observe(&mut self, observe: Option<Observe>) {
312 self.observe = observe;
313 }
314
315 pub fn uri(&self) -> Option<&CoapUri> {
317 self.uri.as_ref().map(|v| v.as_uri())
318 }
319
320 pub fn set_uri<U: Into<CoapUri>>(&mut self, uri: Option<U>) -> Result<(), OptionValueError> {
329 let uri = uri.map(Into::into);
330 if let Some(uri) = uri {
331 self.uri = Some(CoapRequestUri::new_request_uri(uri)?)
332 }
333 Ok(())
334 }
335
336 pub fn set_proxy_uri<U: Into<CoapUri>>(&mut self, uri: Option<U>) -> Result<(), OptionValueError> {
346 let uri = uri.map(Into::into);
347 if let Some(uri) = uri {
348 self.uri = Some(CoapRequestUri::new_proxy_uri(uri)?)
349 }
350 Ok(())
351 }
352
353 pub fn from_message(mut pdu: CoapMessage) -> Result<CoapRequest, MessageConversionError> {
357 let mut host = None;
358 let mut port = None;
359 let mut path = None;
360 let mut query = None;
361 let mut proxy_scheme = None;
362 let mut proxy_uri = None;
363 let mut content_format = None;
364 let mut etag = None;
365 let mut if_match = None;
366 let mut if_none_match = false;
367 let mut accept = None;
368 let mut hop_limit = None;
369 let mut no_response = None;
370 let mut observe = None;
371 let mut additional_opts = Vec::new();
372 for option in pdu.options_iter() {
373 match option {
374 CoapOption::IfMatch(value) => {
375 if if_match.is_none() {
376 if_match = Some(Vec::new());
377 }
378 if_match.as_mut().unwrap().push(value.clone());
379 },
380 CoapOption::IfNoneMatch => {
381 if if_none_match {
382 return Err(MessageConversionError::NonRepeatableOptionRepeated(
383 CoapOptionType::IfNoneMatch,
384 ));
385 }
386 if_none_match = true;
387 },
388 CoapOption::UriHost(value) => {
389 if host.is_some() {
390 return Err(MessageConversionError::NonRepeatableOptionRepeated(
391 CoapOptionType::UriHost,
392 ));
393 }
394 host = Some(value.clone());
395 },
396 CoapOption::UriPort(value) => {
397 if port.is_some() {
398 return Err(MessageConversionError::NonRepeatableOptionRepeated(
399 CoapOptionType::UriPort,
400 ));
401 }
402 port = Some(*value);
403 },
404 CoapOption::UriPath(value) => {
405 if path.is_none() {
406 path = Some(Vec::new());
407 }
408 path.as_mut().unwrap().push(value.clone());
409 },
410 CoapOption::UriQuery(value) => {
411 if query.is_none() {
412 query = Some(Vec::new());
413 }
414 query.as_mut().unwrap().push(value.clone());
415 },
416 CoapOption::LocationPath(_) => {
417 return Err(MessageConversionError::InvalidOptionForMessageType(
418 CoapOptionType::LocationPath,
419 ));
420 },
421 CoapOption::LocationQuery(_) => {
422 return Err(MessageConversionError::InvalidOptionForMessageType(
423 CoapOptionType::LocationQuery,
424 ));
425 },
426 CoapOption::ProxyUri(uri) => {
427 if proxy_uri.is_some() {
428 return Err(MessageConversionError::NonRepeatableOptionRepeated(
429 CoapOptionType::ProxyUri,
430 ));
431 }
432 proxy_uri = Some(uri.clone())
433 },
434 CoapOption::ProxyScheme(scheme) => {
435 if proxy_scheme.is_some() {
436 return Err(MessageConversionError::NonRepeatableOptionRepeated(
437 CoapOptionType::ProxyScheme,
438 ));
439 }
440 proxy_scheme = Some(CoapUriScheme::from_str(scheme)?)
441 },
442 CoapOption::ContentFormat(cformat) => {
443 if content_format.is_some() {
444 return Err(MessageConversionError::NonRepeatableOptionRepeated(
445 CoapOptionType::ContentFormat,
446 ));
447 }
448 content_format = Some(*cformat)
449 },
450 CoapOption::Accept(value) => {
451 if accept.is_some() {
452 return Err(MessageConversionError::NonRepeatableOptionRepeated(
453 CoapOptionType::Accept,
454 ));
455 }
456 accept = Some(*value);
457 },
458 CoapOption::Size1(_) => {},
460 CoapOption::Size2(_) => {
461 return Err(MessageConversionError::InvalidOptionForMessageType(
462 CoapOptionType::Size2,
463 ));
464 },
465 CoapOption::Block1(_) => {},
467 CoapOption::Block2(_) => {
468 return Err(MessageConversionError::InvalidOptionForMessageType(
469 CoapOptionType::Block2,
470 ));
471 },
472 CoapOption::HopLimit(value) => {
473 if hop_limit.is_some() {
474 return Err(MessageConversionError::NonRepeatableOptionRepeated(
475 CoapOptionType::HopLimit,
476 ));
477 }
478 hop_limit = Some(*value);
479 },
480 CoapOption::NoResponse(value) => {
481 if no_response.is_some() {
482 return Err(MessageConversionError::NonRepeatableOptionRepeated(
483 CoapOptionType::NoResponse,
484 ));
485 }
486 no_response = Some(*value);
487 },
488 CoapOption::ETag(value) => {
489 if etag.is_none() {
490 etag = Some(Vec::new());
491 }
492 etag.as_mut().unwrap().push(value.clone());
493 },
494 CoapOption::MaxAge(_value) => {
495 return Err(MessageConversionError::InvalidOptionForMessageType(
496 CoapOptionType::MaxAge,
497 ));
498 },
499 CoapOption::Observe(value) => {
500 if observe.is_some() {
501 return Err(MessageConversionError::NonRepeatableOptionRepeated(
502 CoapOptionType::MaxAge,
503 ));
504 }
505 observe = Some(*value);
506 },
507 CoapOption::Other(n, v) => {
509 additional_opts.push(CoapOption::Other(*n, v.clone()));
510 },
511 }
512 }
513 pdu.clear_options();
514 for opt in additional_opts {
515 pdu.add_option(opt);
516 }
517 if proxy_scheme.is_some() && proxy_uri.is_some() {
518 return Err(MessageConversionError::InvalidOptionCombination(
519 CoapOptionType::ProxyScheme,
520 CoapOptionType::ProxyUri,
521 ));
522 }
523 let uri = if let Some(proxy_uri) = proxy_uri {
524 Some(CoapUri::try_from_url(Url::parse(&proxy_uri)?)?)
525 } else {
526 Some(CoapUri::new(
527 proxy_scheme,
528 host.map(|v| CoapUriHost::from_str(v.as_str()).unwrap()),
529 port,
530 path,
531 query,
532 ))
533 }
534 .map(|uri| {
535 if uri.scheme().is_some() {
536 CoapRequestUri::new_proxy_uri(uri)
537 } else {
538 CoapRequestUri::new_request_uri(uri)
539 }
540 });
541 let uri = if let Some(uri) = uri {
542 Some(uri.map_err(|e| MessageConversionError::InvalidOptionValue(None, e))?)
543 } else {
544 None
545 };
546 Ok(CoapRequest {
547 pdu,
548 uri,
549 accept,
550 etag,
551 if_match,
552 content_format,
553 if_none_match,
554 hop_limit,
555 no_response,
556 observe,
557 })
558 }
559
560 pub fn into_message(mut self) -> CoapMessage {
562 if let Some(req_uri) = self.uri {
563 req_uri.into_options().into_iter().for_each(|v| self.pdu.add_option(v));
564 }
565 if let Some(accept) = self.accept {
566 self.pdu.add_option(CoapOption::Accept(accept))
567 }
568 if let Some(etags) = self.etag {
569 for etag in etags {
570 self.pdu.add_option(CoapOption::ETag(etag));
571 }
572 }
573 if let Some(if_match) = self.if_match {
574 for match_expr in if_match {
575 self.pdu.add_option(CoapOption::IfMatch(match_expr));
576 }
577 }
578 if let Some(content_format) = self.content_format {
579 self.pdu.add_option(CoapOption::ContentFormat(content_format));
580 }
581 if self.if_none_match {
582 self.pdu.add_option(CoapOption::IfNoneMatch);
583 }
584 if let Some(hop_limit) = self.hop_limit {
585 self.pdu.add_option(CoapOption::HopLimit(hop_limit));
586 }
587 if let Some(no_response) = self.no_response {
588 self.pdu.add_option(CoapOption::NoResponse(no_response));
589 }
590 if let Some(observe) = self.observe {
591 self.pdu.add_option(CoapOption::Observe(observe));
592 }
593 self.pdu
594 }
595}
596
597impl CoapMessageCommon for CoapRequest {
598 fn set_code<C: Into<CoapMessageCode>>(&mut self, code: C) {
603 match code.into() {
604 CoapMessageCode::Request(req) => self.pdu.set_code(CoapMessageCode::Request(req)),
605 CoapMessageCode::Response(_) | CoapMessageCode::Empty => {
606 panic!("attempted to set message code of request to value that is not a request code")
607 },
608 }
609 }
610
611 fn as_message(&self) -> &CoapMessage {
612 &self.pdu
613 }
614
615 fn as_message_mut(&mut self) -> &mut CoapMessage {
616 &mut self.pdu
617 }
618}