1#![allow(clippy::use_self)]
23
24use alloc::{string::String, vec::Vec};
25use core::{fmt, str};
26
27#[cfg(feature = "serde")]
28use serde::{Deserialize, Serialize};
29use url::Url;
30
31use crate::{
32 error::{ProtoError, ProtoResult},
33 rr::{RData, RecordData, RecordDataDecodable, RecordType, domain::Name},
34 serialize::binary::*,
35};
36
37#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
41#[derive(Debug, PartialEq, Eq, Hash, Clone)]
42pub struct CAA {
43 pub(crate) issuer_critical: bool,
44 pub(crate) reserved_flags: u8,
45 pub(crate) tag: Property,
46 pub(crate) raw_tag: Vec<u8>,
47 pub(crate) value: Value,
48 pub(crate) raw_value: Vec<u8>,
49}
50
51impl CAA {
52 fn issue(
53 issuer_critical: bool,
54 tag: Property,
55 name: Option<Name>,
56 options: Vec<KeyValue>,
57 ) -> Self {
58 assert!(tag.is_issue() || tag.is_issuewild());
59
60 let raw_tag = tag.as_str().as_bytes().to_vec();
61 let raw_value = encode_issuer_value(name.as_ref(), &options);
62
63 Self {
64 issuer_critical,
65 reserved_flags: 0,
66 tag,
67 raw_tag,
68 value: Value::Issuer(name, options),
69 raw_value,
70 }
71 }
72
73 pub fn new_issue(issuer_critical: bool, name: Option<Name>, options: Vec<KeyValue>) -> Self {
81 Self::issue(issuer_critical, Property::Issue, name, options)
82 }
83
84 pub fn new_issuewild(
92 issuer_critical: bool,
93 name: Option<Name>,
94 options: Vec<KeyValue>,
95 ) -> Self {
96 Self::issue(issuer_critical, Property::IssueWild, name, options)
97 }
98
99 pub fn new_iodef(issuer_critical: bool, url: Url) -> Self {
106 let raw_value = url.as_str().as_bytes().to_vec();
107 Self {
108 issuer_critical,
109 reserved_flags: 0,
110 tag: Property::Iodef,
111 raw_tag: Property::Iodef.as_str().as_bytes().to_vec(),
112 value: Value::Url(url),
113 raw_value,
114 }
115 }
116
117 pub fn issuer_critical(&self) -> bool {
119 self.issuer_critical
120 }
121
122 pub fn set_issuer_critical(&mut self, issuer_critical: bool) {
125 self.issuer_critical = issuer_critical;
126 }
127
128 pub fn flags(&self) -> u8 {
130 let mut flags = self.reserved_flags & 0b0111_1111;
131 if self.issuer_critical {
132 flags |= 0b1000_0000;
133 }
134 flags
135 }
136
137 pub fn tag(&self) -> &Property {
139 &self.tag
140 }
141
142 pub fn set_tag(&mut self, tag: Property) {
144 self.raw_tag = tag.as_str().as_bytes().to_vec();
145 self.tag = tag;
146 }
147
148 #[deprecated = "See value_as_issue(), value_as_iodef(), or raw_value() instead"]
150 pub fn value(&self) -> &Value {
151 &self.value
152 }
153
154 pub fn set_value(&mut self, value: Value) {
156 self.raw_value = match &value {
157 Value::Issuer(name, key_values) => encode_issuer_value(name.as_ref(), key_values),
158 Value::Url(url) => url.as_str().as_bytes().to_vec(),
159 Value::Unknown(value) => value.clone(),
160 };
161 self.value = value;
162 }
163
164 pub fn value_as_issue(&self) -> ProtoResult<(Option<Name>, Vec<KeyValue>)> {
169 match &self.value {
171 Value::Issuer(name, key_values) => Ok((name.clone(), key_values.clone())),
172 _ => Err("CAA property tag is not 'issue' or 'issuewild'".into()),
173 }
174 }
175
176 pub fn value_as_iodef(&self) -> ProtoResult<Url> {
180 match &self.value {
182 Value::Url(url) => Ok(url.clone()),
183 _ => Err("CAA property tag is not 'iodef'".into()),
184 }
185 }
186
187 pub fn raw_value(&self) -> &[u8] {
189 &self.raw_value
190 }
191}
192
193#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
195#[derive(Debug, PartialEq, Eq, Hash, Clone)]
196pub enum Property {
197 #[cfg_attr(feature = "serde", serde(rename = "issue"))]
203 Issue,
204 #[cfg_attr(feature = "serde", serde(rename = "issuewild"))]
210 IssueWild,
211 #[cfg_attr(feature = "serde", serde(rename = "iodef"))]
218 Iodef,
219 Unknown(String),
221}
222
223impl Property {
224 pub fn as_str(&self) -> &str {
226 match self {
227 Self::Issue => "issue",
228 Self::IssueWild => "issuewild",
229 Self::Iodef => "iodef",
230 Self::Unknown(property) => property,
231 }
232 }
233
234 pub fn is_issue(&self) -> bool {
236 matches!(*self, Self::Issue)
237 }
238
239 pub fn is_issuewild(&self) -> bool {
241 matches!(*self, Self::IssueWild)
242 }
243
244 pub fn is_iodef(&self) -> bool {
246 matches!(*self, Self::Iodef)
247 }
248
249 pub fn is_unknown(&self) -> bool {
251 matches!(*self, Self::Unknown(_))
252 }
253}
254
255impl From<String> for Property {
256 fn from(tag: String) -> Self {
257 let lower = tag.to_ascii_lowercase();
260 match &lower as &str {
261 "issue" => return Self::Issue,
262 "issuewild" => return Self::IssueWild,
263 "iodef" => return Self::Iodef,
264 &_ => (),
265 }
266
267 Self::Unknown(tag)
268 }
269}
270
271#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
281#[derive(Debug, PartialEq, Eq, Hash, Clone)]
282pub enum Value {
284 Issuer(Option<Name>, Vec<KeyValue>),
286 Url(Url),
288 Unknown(Vec<u8>),
290}
291
292impl Value {
293 pub fn is_issuer(&self) -> bool {
295 matches!(*self, Value::Issuer(..))
296 }
297
298 pub fn is_url(&self) -> bool {
300 matches!(*self, Value::Url(..))
301 }
302
303 pub fn is_unknown(&self) -> bool {
305 matches!(*self, Value::Unknown(..))
306 }
307}
308
309pub(crate) fn read_value(
310 tag: &Property,
311 decoder: &mut BinDecoder<'_>,
312 value_len: Restrict<u16>,
313) -> ProtoResult<Value> {
314 let value_len = value_len.map(|u| u as usize).unverified();
315 match tag {
316 Property::Issue | Property::IssueWild => {
317 let slice = decoder.read_slice(value_len)?.unverified();
318 Ok(match read_issuer(slice) {
319 Ok(value) => Value::Issuer(value.0, value.1),
320 Err(_) => Value::Unknown(slice.to_vec()),
321 })
322 }
323 Property::Iodef => {
324 let url = decoder.read_slice(value_len)?.unverified();
325 Ok(match read_iodef(url) {
326 Ok(url) => Value::Url(url),
327 Err(_) => Value::Unknown(url.to_vec()),
328 })
329 }
330 Property::Unknown(_) => Ok(Value::Unknown(
331 decoder.read_vec(value_len)?.unverified(),
332 )),
333 }
334}
335
336fn encode_issuer_value(name: Option<&Name>, key_values: &[KeyValue]) -> Vec<u8> {
337 let mut output = Vec::new();
338
339 if let Some(name) = name {
341 let name = name.to_ascii();
342 output.extend_from_slice(name.as_bytes());
343 }
344
345 if name.is_none() && key_values.is_empty() {
347 output.push(b';');
348 return output;
349 }
350
351 for key_value in key_values {
352 output.push(b';');
353 output.push(b' ');
354 output.extend_from_slice(key_value.key.as_bytes());
355 output.push(b'=');
356 output.extend_from_slice(key_value.value.as_bytes());
357 }
358
359 output
360}
361
362enum ParseNameKeyPairState {
363 BeforeKey(Vec<KeyValue>),
364 Key {
365 first_char: bool,
366 key: String,
367 key_values: Vec<KeyValue>,
368 },
369 Value {
370 key: String,
371 value: String,
372 key_values: Vec<KeyValue>,
373 },
374}
375
376pub fn read_issuer(bytes: &[u8]) -> ProtoResult<(Option<Name>, Vec<KeyValue>)> {
462 let mut byte_iter = bytes.iter();
463
464 let name: Option<Name> = {
466 let take_name = byte_iter.by_ref().take_while(|ch| char::from(**ch) != ';');
467 let name_str = take_name.cloned().collect::<Vec<u8>>();
468
469 if !name_str.is_empty() {
470 let name_str = str::from_utf8(&name_str)?;
471 Some(Name::from_ascii(name_str)?)
472 } else {
473 None
474 }
475 };
476
477 let mut state = ParseNameKeyPairState::BeforeKey(vec![]);
479
480 for ch in byte_iter {
482 match state {
483 ParseNameKeyPairState::BeforeKey(key_values) => {
485 match char::from(*ch) {
486 ';' | ' ' | '\u{0009}' => state = ParseNameKeyPairState::BeforeKey(key_values),
488 ch if ch.is_ascii_alphanumeric() && ch != '=' => {
489 let mut key = String::new();
491 key.push(ch);
492
493 state = ParseNameKeyPairState::Key {
494 first_char: true,
495 key,
496 key_values,
497 }
498 }
499 ch => return Err(format!("bad character in CAA issuer key: {ch}").into()),
500 }
501 }
502 ParseNameKeyPairState::Key {
503 first_char,
504 mut key,
505 key_values,
506 } => {
507 match char::from(*ch) {
508 '=' => {
510 let value = String::new();
511 state = ParseNameKeyPairState::Value {
512 key,
513 value,
514 key_values,
515 }
516 }
517 ch if (ch.is_ascii_alphanumeric() || (!first_char && ch == '-'))
519 && ch != '='
520 && ch != ';' =>
521 {
522 key.push(ch);
523 state = ParseNameKeyPairState::Key {
524 first_char: false,
525 key,
526 key_values,
527 }
528 }
529 ch => return Err(format!("bad character in CAA issuer key: {ch}").into()),
530 }
531 }
532 ParseNameKeyPairState::Value {
533 key,
534 mut value,
535 mut key_values,
536 } => {
537 match char::from(*ch) {
538 ';' => {
540 key_values.push(KeyValue { key, value });
541 state = ParseNameKeyPairState::BeforeKey(key_values);
542 }
543 ch if ('\x21'..='\x3A').contains(&ch) || ('\x3C'..='\x7E').contains(&ch) => {
547 value.push(ch);
548
549 state = ParseNameKeyPairState::Value {
550 key,
551 value,
552 key_values,
553 }
554 }
555 ch => return Err(format!("bad character in CAA issuer value: '{ch}'").into()),
556 }
557 }
558 }
559 }
560
561 let key_values = match state {
564 ParseNameKeyPairState::BeforeKey(key_values) => key_values,
565 ParseNameKeyPairState::Value {
566 key,
567 value,
568 mut key_values,
569 } => {
570 key_values.push(KeyValue { key, value });
571 key_values
572 }
573 ParseNameKeyPairState::Key { key, .. } => {
574 return Err(format!("key missing value: {key}").into());
575 }
576 };
577
578 Ok((name, key_values))
579}
580
581pub fn read_iodef(url: &[u8]) -> ProtoResult<Url> {
620 let url = str::from_utf8(url)?;
621 let url = Url::parse(url)?;
622 Ok(url)
623}
624
625#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
630#[derive(Debug, PartialEq, Eq, Hash, Clone)]
631pub struct KeyValue {
632 key: String,
633 value: String,
634}
635
636impl KeyValue {
637 pub fn new<K: Into<String>, V: Into<String>>(key: K, value: V) -> Self {
639 Self {
640 key: key.into(),
641 value: value.into(),
642 }
643 }
644
645 pub fn key(&self) -> &str {
647 &self.key
648 }
649
650 pub fn value(&self) -> &str {
652 &self.value
653 }
654}
655
656fn read_tag(decoder: &mut BinDecoder<'_>, len: Restrict<u8>) -> ProtoResult<String> {
658 let len = len
659 .map(|len| len as usize)
660 .verify_unwrap(|len| *len > 0 && *len <= 15)
661 .map_err(|_| ProtoError::from("CAA tag length out of bounds, 1-15"))?;
662 let mut tag = String::with_capacity(len);
663
664 for _ in 0..len {
665 let ch = decoder
666 .pop()?
667 .map(char::from)
668 .verify_unwrap(|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9'))
669 .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?;
670
671 tag.push(ch);
672 }
673
674 Ok(tag)
675}
676
677fn emit_tag(buf: &mut [u8], tag: &[u8]) -> ProtoResult<u8> {
679 let len = tag.len();
680 if len > u8::MAX as usize {
681 return Err(format!("CAA property too long: {len}").into());
682 }
683 if buf.len() < len {
684 return Err(format!(
685 "insufficient capacity in CAA buffer: {} for tag: {}",
686 buf.len(),
687 len
688 )
689 .into());
690 }
691
692 let buf = &mut buf[0..len];
694 buf.copy_from_slice(tag);
695
696 Ok(len as u8)
697}
698
699impl BinEncodable for CAA {
700 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
701 encoder.emit(self.flags())?;
702 let mut tag_buf = [0_u8; u8::MAX as usize];
704 let len = emit_tag(&mut tag_buf, &self.raw_tag)?;
705
706 encoder.emit(len)?;
708 encoder.emit_vec(&tag_buf[0..len as usize])?;
709 encoder.emit_vec(&self.raw_value)?;
710
711 Ok(())
712 }
713}
714
715impl<'r> RecordDataDecodable<'r> for CAA {
716 fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<CAA> {
785 let flags = decoder.read_u8()?.unverified();
786
787 let issuer_critical = (flags & 0b1000_0000) != 0;
788 let reserved_flags = flags & 0b0111_1111;
789
790 let tag_len = decoder.read_u8()?;
791 let value_len = length
792 .checked_sub(u16::from(tag_len.unverified()))
793 .checked_sub(2)
794 .map_err(|_| ProtoError::from("CAA tag character(s) out of bounds"))?
795 .unverified();
796
797 let tag = read_tag(decoder, tag_len)?;
798 let raw_tag = tag.clone().into_bytes();
799 let tag = Property::from(tag);
800
801 let raw_value =
802 decoder.read_vec(value_len as usize)?.unverified();
803
804 let mut value_decoder = BinDecoder::new(&raw_value);
805 let value = read_value(&tag, &mut value_decoder, Restrict::new(value_len))?;
806
807 Ok(CAA {
808 issuer_critical,
809 reserved_flags,
810 tag,
811 raw_tag,
812 value,
813 raw_value,
814 })
815 }
816}
817
818impl RecordData for CAA {
819 fn try_from_rdata(data: RData) -> Result<Self, RData> {
820 match data {
821 RData::CAA(csync) => Ok(csync),
822 _ => Err(data),
823 }
824 }
825
826 fn try_borrow(data: &RData) -> Option<&Self> {
827 match data {
828 RData::CAA(csync) => Some(csync),
829 _ => None,
830 }
831 }
832
833 fn record_type(&self) -> RecordType {
834 RecordType::CAA
835 }
836
837 fn into_rdata(self) -> RData {
838 RData::CAA(self)
839 }
840}
841
842impl fmt::Display for Property {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
844 let s = match self {
845 Self::Issue => "issue",
846 Self::IssueWild => "issuewild",
847 Self::Iodef => "iodef",
848 Self::Unknown(s) => s,
849 };
850
851 f.write_str(s)
852 }
853}
854
855impl fmt::Display for Value {
857 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
859 f.write_str("\"")?;
860
861 match self {
862 Value::Issuer(name, values) => {
863 if let Some(name) = name {
864 write!(f, "{name}")?;
865 }
866 for value in values.iter() {
867 write!(f, "; {value}")?;
868 }
869 }
870 Value::Url(url) => write!(f, "{url}")?,
871 Value::Unknown(v) => match str::from_utf8(v) {
872 Ok(text) => write!(f, "{text}")?,
873 Err(_) => return Err(fmt::Error),
874 },
875 }
876
877 f.write_str("\"")
878 }
879}
880
881impl fmt::Display for KeyValue {
882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
883 f.write_str(&self.key)?;
884 if !self.value.is_empty() {
885 write!(f, "={}", self.value)?;
886 }
887
888 Ok(())
889 }
890}
891
892impl fmt::Display for CAA {
894 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
895 write!(
896 f,
897 "{flags} {tag} \"{value}\"",
898 flags = self.flags(),
899 tag = String::from_utf8_lossy(&self.raw_tag),
900 value = String::from_utf8_lossy(&self.raw_value)
901 )
902 }
903}
904
905#[cfg(test)]
906mod tests {
907 #![allow(clippy::dbg_macro, clippy::print_stdout)]
908
909 use alloc::{str, string::ToString};
910 #[cfg(feature = "std")]
911 use std::{dbg, println};
912
913 use super::*;
914
915 #[test]
916 fn test_read_tag() {
917 let ok_under15 = b"abcxyzABCXYZ019";
918 let mut decoder = BinDecoder::new(ok_under15);
919
920 let read = read_tag(&mut decoder, Restrict::new(ok_under15.len() as u8))
921 .expect("failed to read tag");
922
923 assert_eq!(str::from_utf8(ok_under15).unwrap(), read);
924 }
925
926 #[test]
927 fn test_bad_tag() {
928 let bad_under15 = b"-";
929 let mut decoder = BinDecoder::new(bad_under15);
930
931 assert!(read_tag(&mut decoder, Restrict::new(bad_under15.len() as u8)).is_err());
932 }
933
934 #[test]
935 fn test_too_short_tag() {
936 let too_short = b"";
937 let mut decoder = BinDecoder::new(too_short);
938
939 assert!(read_tag(&mut decoder, Restrict::new(too_short.len() as u8)).is_err());
940 }
941
942 #[test]
943 fn test_too_long_tag() {
944 let too_long = b"0123456789abcdef";
945 let mut decoder = BinDecoder::new(too_long);
946
947 assert!(read_tag(&mut decoder, Restrict::new(too_long.len() as u8)).is_err());
948 }
949
950 #[test]
951 fn test_from_str_property() {
952 assert_eq!(Property::from("Issue".to_string()), Property::Issue);
953 assert_eq!(Property::from("issueWild".to_string()), Property::IssueWild);
954 assert_eq!(Property::from("iodef".to_string()), Property::Iodef);
955 assert_eq!(
956 Property::from("unknown".to_string()),
957 Property::Unknown("unknown".to_string())
958 );
959 }
960
961 #[test]
962 fn test_read_issuer() {
963 assert_eq!(
965 read_issuer(b"ca.example.net; account=230123").unwrap(),
966 (
967 Some(Name::parse("ca.example.net", None).unwrap()),
968 vec![KeyValue {
969 key: "account".to_string(),
970 value: "230123".to_string(),
971 }],
972 )
973 );
974
975 assert_eq!(
976 read_issuer(b"ca.example.net").unwrap(),
977 (Some(Name::parse("ca.example.net", None,).unwrap(),), vec![],)
978 );
979 assert_eq!(
980 read_issuer(b"ca.example.net; policy=ev").unwrap(),
981 (
982 Some(Name::parse("ca.example.net", None).unwrap(),),
983 vec![KeyValue {
984 key: "policy".to_string(),
985 value: "ev".to_string(),
986 }],
987 )
988 );
989 assert_eq!(
990 read_issuer(b"ca.example.net; account=230123; policy=ev").unwrap(),
991 (
992 Some(Name::parse("ca.example.net", None).unwrap(),),
993 vec![
994 KeyValue {
995 key: "account".to_string(),
996 value: "230123".to_string(),
997 },
998 KeyValue {
999 key: "policy".to_string(),
1000 value: "ev".to_string(),
1001 },
1002 ],
1003 )
1004 );
1005 assert_eq!(
1006 read_issuer(b"example.net; account-uri=https://example.net/account/1234; validation-methods=dns-01").unwrap(),
1007 (
1008 Some(Name::parse("example.net", None).unwrap(),),
1009 vec![
1010 KeyValue {
1011 key: "account-uri".to_string(),
1012 value: "https://example.net/account/1234".to_string(),
1013 },
1014 KeyValue {
1015 key: "validation-methods".to_string(),
1016 value: "dns-01".to_string(),
1017 },
1018 ],
1019 )
1020 );
1021 assert_eq!(read_issuer(b";").unwrap(), (None, vec![]));
1022 read_issuer(b"example.com; param=\xff").unwrap_err();
1023 }
1024
1025 #[test]
1026 fn test_read_iodef() {
1027 assert_eq!(
1028 read_iodef(b"mailto:security@example.com").unwrap(),
1029 Url::parse("mailto:security@example.com").unwrap()
1030 );
1031 assert_eq!(
1032 read_iodef(b"https://iodef.example.com/").unwrap(),
1033 Url::parse("https://iodef.example.com/").unwrap()
1034 );
1035 }
1036
1037 fn test_encode_decode(rdata: CAA) {
1038 let mut bytes = Vec::new();
1039 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
1040 rdata.emit(&mut encoder).expect("failed to emit caa");
1041 let bytes = encoder.into_bytes();
1042
1043 #[cfg(feature = "std")]
1044 println!("bytes: {bytes:?}");
1045
1046 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
1047 let read_rdata = CAA::read_data(&mut decoder, Restrict::new(bytes.len() as u16))
1048 .expect("failed to read back");
1049 assert_eq!(rdata, read_rdata);
1050 }
1051
1052 #[test]
1053 fn test_encode_decode_issue() {
1054 test_encode_decode(CAA::new_issue(true, None, vec![]));
1055 test_encode_decode(CAA::new_issue(
1056 true,
1057 Some(Name::parse("example.com", None).unwrap()),
1058 vec![],
1059 ));
1060 test_encode_decode(CAA::new_issue(
1061 true,
1062 Some(Name::parse("example.com", None).unwrap()),
1063 vec![KeyValue::new("key", "value")],
1064 ));
1065 test_encode_decode(CAA::new_issue(
1067 true,
1068 None,
1069 vec![KeyValue::new("key", "value")],
1070 ));
1071 test_encode_decode(CAA::new_issue(
1073 true,
1074 Some(Name::parse("example.com.", None).unwrap()),
1075 vec![],
1076 ));
1077 test_encode_decode(CAA {
1079 issuer_critical: false,
1080 reserved_flags: 0,
1081 tag: Property::Issue,
1082 raw_tag: b"issue".to_vec(),
1083 value: Value::Unknown(b"%%%%%".to_vec()),
1084 raw_value: b"%%%%%".to_vec(),
1085 });
1086 }
1087
1088 #[test]
1089 fn test_encode_decode_issuewild() {
1090 test_encode_decode(CAA::new_issuewild(false, None, vec![]));
1091 }
1093
1094 #[test]
1095 fn test_encode_decode_iodef() {
1096 test_encode_decode(CAA::new_iodef(
1097 true,
1098 Url::parse("https://www.example.com").unwrap(),
1099 ));
1100 test_encode_decode(CAA::new_iodef(
1101 false,
1102 Url::parse("mailto:root@example.com").unwrap(),
1103 ));
1104 test_encode_decode(CAA {
1106 issuer_critical: false,
1107 reserved_flags: 0,
1108 tag: Property::Iodef,
1109 raw_tag: b"iodef".to_vec(),
1110 value: Value::Unknown(vec![0xff]),
1111 raw_value: vec![0xff],
1112 });
1113 }
1114
1115 #[test]
1116 fn test_encode_decode_unknown() {
1117 test_encode_decode(CAA {
1118 issuer_critical: true,
1119 reserved_flags: 0,
1120 tag: Property::Unknown("tbs".to_string()),
1121 raw_tag: b"tbs".to_vec(),
1122 value: Value::Unknown(b"Unknown".to_vec()),
1123 raw_value: b"Unknown".to_vec(),
1124 });
1125 }
1126
1127 fn test_encode(rdata: CAA, encoded: &[u8]) {
1128 let mut bytes = Vec::new();
1129 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
1130 rdata.emit(&mut encoder).expect("failed to emit caa");
1131 let bytes = encoder.into_bytes();
1132 assert_eq!(bytes as &[u8], encoded);
1133 }
1134
1135 #[test]
1136 fn test_encode_non_fqdn() {
1137 let name_bytes: &[u8] = b"issueexample.com";
1138 let header: &[u8] = &[128, 5];
1139 let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1140
1141 test_encode(
1142 CAA::new_issue(
1143 true,
1144 Some(Name::parse("example.com", None).unwrap()),
1145 vec![],
1146 ),
1147 &encoded,
1148 );
1149 }
1150
1151 #[test]
1152 fn test_encode_fqdn() {
1153 let name_bytes: &[u8] = b"issueexample.com.";
1154 let header: [u8; 2] = [128, 5];
1155 let encoded: Vec<u8> = header.iter().chain(name_bytes.iter()).cloned().collect();
1156
1157 test_encode(
1158 CAA::new_issue(
1159 true,
1160 Some(Name::parse("example.com.", None).unwrap()),
1161 vec![],
1162 ),
1163 &encoded,
1164 );
1165 }
1166
1167 #[test]
1168 fn test_to_string() {
1169 let deny = CAA::new_issue(false, None, vec![]);
1170 assert_eq!(deny.to_string(), "0 issue \";\"");
1171
1172 let empty_options = CAA::new_issue(
1173 false,
1174 Some(Name::parse("example.com", None).unwrap()),
1175 vec![],
1176 );
1177 assert_eq!(empty_options.to_string(), "0 issue \"example.com\"");
1178
1179 let one_option = CAA::new_issue(
1180 false,
1181 Some(Name::parse("example.com", None).unwrap()),
1182 vec![KeyValue::new("one", "1")],
1183 );
1184 assert_eq!(one_option.to_string(), "0 issue \"example.com; one=1\"");
1185
1186 let two_options = CAA::new_issue(
1187 false,
1188 Some(Name::parse("example.com", None).unwrap()),
1189 vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1190 );
1191 assert_eq!(
1192 two_options.to_string(),
1193 "0 issue \"example.com; one=1; two=2\""
1194 );
1195
1196 let flag_set = CAA::new_issue(
1197 true,
1198 Some(Name::parse("example.com", None).unwrap()),
1199 vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1200 );
1201 assert_eq!(
1202 flag_set.to_string(),
1203 "128 issue \"example.com; one=1; two=2\""
1204 );
1205
1206 let empty_domain = CAA::new_issue(
1207 false,
1208 None,
1209 vec![KeyValue::new("one", "1"), KeyValue::new("two", "2")],
1210 );
1211 assert_eq!(empty_domain.to_string(), "0 issue \"; one=1; two=2\"");
1212
1213 assert_eq!(
1215 CAA::new_issue(
1216 false,
1217 Some(Name::parse("ca.example.net", None).unwrap()),
1218 vec![KeyValue::new("account", "230123")]
1219 )
1220 .to_string(),
1221 "0 issue \"ca.example.net; account=230123\""
1222 );
1223 assert_eq!(
1224 CAA::new_issue(
1225 false,
1226 Some(Name::parse("ca.example.net", None).unwrap()),
1227 vec![KeyValue::new("policy", "ev")]
1228 )
1229 .to_string(),
1230 "0 issue \"ca.example.net; policy=ev\""
1231 );
1232 assert_eq!(
1233 CAA::new_iodef(false, Url::parse("mailto:security@example.com").unwrap()).to_string(),
1234 "0 iodef \"mailto:security@example.com\""
1235 );
1236 assert_eq!(
1237 CAA::new_iodef(false, Url::parse("https://iodef.example.com/").unwrap()).to_string(),
1238 "0 iodef \"https://iodef.example.com/\""
1239 );
1240 let unknown = CAA {
1241 issuer_critical: true,
1242 reserved_flags: 0,
1243 tag: Property::from("tbs".to_string()),
1244 raw_tag: b"tbs".to_vec(),
1245 value: Value::Unknown(b"Unknown".to_vec()),
1246 raw_value: b"Unknown".to_vec(),
1247 };
1248 assert_eq!(unknown.to_string(), "128 tbs \"Unknown\"");
1249 }
1250
1251 #[test]
1252 fn test_unicode_kv() {
1253 const MESSAGE: &[u8] = &[
1254 32, 5, 105, 115, 115, 117, 101, 103, 103, 103, 102, 71, 46, 110, 110, 115, 115, 117,
1255 48, 110, 45, 59, 32, 32, 255, 61, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1256 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1257 ];
1258
1259 let mut decoder = BinDecoder::new(MESSAGE);
1260 let caa = CAA::read_data(&mut decoder, Restrict::new(MESSAGE.len() as u16)).unwrap();
1261 assert!(!caa.issuer_critical());
1262 assert_eq!(*caa.tag(), Property::Issue);
1263 match &caa.value {
1264 Value::Unknown(bytes) => {
1265 assert_eq!(bytes, &MESSAGE[7..]);
1266 }
1267 _ => panic!("wrong value type: {:?}", caa.value),
1268 }
1269 assert_eq!(caa.raw_value, &MESSAGE[7..]);
1270 }
1271
1272 #[test]
1273 fn test_name_non_ascii_character_escaped_dots_roundtrip() {
1274 const MESSAGE: &[u8] = b"\x00\x05issue\xe5\x85\x9edomain\\.\\.name";
1275 let caa = CAA::read_data(
1276 &mut BinDecoder::new(MESSAGE),
1277 Restrict::new(u16::try_from(MESSAGE.len()).unwrap()),
1278 )
1279 .unwrap();
1280 #[cfg(feature = "std")]
1281 dbg!(&caa.value);
1282
1283 let mut encoded = Vec::new();
1284 caa.emit(&mut BinEncoder::new(&mut encoded)).unwrap();
1285
1286 let caa_round_trip = CAA::read_data(
1287 &mut BinDecoder::new(&encoded),
1288 Restrict::new(u16::try_from(encoded.len()).unwrap()),
1289 )
1290 .unwrap();
1291 #[cfg(feature = "std")]
1292 dbg!(&caa_round_trip.value);
1293
1294 assert_eq!(caa, caa_round_trip);
1295 }
1296
1297 #[test]
1298 fn test_reserved_flags_round_trip() {
1299 let mut original = *b"\x00\x05issueexample.com";
1300 for flags in 0..=u8::MAX {
1301 original[0] = flags;
1302 let caa = CAA::read_data(
1303 &mut BinDecoder::new(&original),
1304 Restrict::new(u16::try_from(original.len()).unwrap()),
1305 )
1306 .unwrap();
1307
1308 let mut encoded = Vec::new();
1309 caa.emit(&mut BinEncoder::new(&mut encoded)).unwrap();
1310 assert_eq!(original.as_slice(), &encoded);
1311 }
1312 }
1313}