1use bytes::{BufMut, Bytes, BytesMut};
2use std::fmt;
3use std::net::{Ipv4Addr, Ipv6Addr};
4use std::str::FromStr;
5
6pub const DOMAINNAME_MAX_LEN: usize = 255;
9
10pub const LABEL_MAX_LEN: usize = 63;
12
13pub const HEADER_MASK_QR: u8 = 0b1000_0000;
15
16pub const HEADER_MASK_OPCODE: u8 = 0b0111_1000;
18
19pub const HEADER_OFFSET_OPCODE: usize = 3;
21
22pub const HEADER_MASK_AA: u8 = 0b0000_0100;
24
25pub const HEADER_MASK_TC: u8 = 0b0000_0010;
27
28pub const HEADER_MASK_RD: u8 = 0b0000_0001;
30
31pub const HEADER_MASK_RA: u8 = 0b1000_0000;
33
34pub const HEADER_MASK_RCODE: u8 = 0b0000_1111;
36
37pub const HEADER_OFFSET_RCODE: usize = 0;
39
40#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
58#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
59pub struct Message {
60 pub header: Header,
61 pub questions: Vec<Question>,
62 pub answers: Vec<ResourceRecord>,
63 pub authority: Vec<ResourceRecord>,
64 pub additional: Vec<ResourceRecord>,
65}
66
67impl Message {
68 pub fn make_response(&self) -> Self {
69 Self {
70 header: Header {
71 id: self.header.id,
72 is_response: true,
73 opcode: self.header.opcode,
74 is_authoritative: false,
75 is_truncated: false,
76 recursion_desired: self.header.recursion_desired,
77 recursion_available: true,
78 rcode: Rcode::NoError,
79 },
80 questions: self.questions.clone(),
81 answers: Vec::new(),
82 authority: Vec::new(),
83 additional: Vec::new(),
84 }
85 }
86
87 pub fn make_format_error_response(id: u16) -> Self {
88 Self {
89 header: Header {
90 id,
91 is_response: true,
92 opcode: Opcode::Standard,
93 is_authoritative: false,
94 is_truncated: false,
95 recursion_desired: false,
96 recursion_available: true,
97 rcode: Rcode::FormatError,
98 },
99 questions: Vec::new(),
100 answers: Vec::new(),
101 authority: Vec::new(),
102 additional: Vec::new(),
103 }
104 }
105
106 pub fn from_question(id: u16, question: Question) -> Self {
107 Self {
108 header: Header {
109 id,
110 is_response: false,
111 opcode: Opcode::Standard,
112 is_authoritative: false,
113 is_truncated: false,
114 recursion_desired: false,
115 recursion_available: false,
116 rcode: Rcode::NoError,
117 },
118 questions: vec![question],
119 answers: Vec::new(),
120 authority: Vec::new(),
121 additional: Vec::new(),
122 }
123 }
124}
125
126#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
152#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
153pub struct Header {
154 pub id: u16,
159
160 pub is_response: bool,
163
164 pub opcode: Opcode,
176
177 pub is_authoritative: bool,
186
187 pub is_truncated: bool,
191
192 pub recursion_desired: bool,
197
198 pub recursion_available: bool,
202
203 pub rcode: Rcode,
229}
230
231#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
251#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
252pub struct Question {
253 pub name: DomainName,
259
260 pub qtype: QueryType,
265
266 pub qclass: QueryClass,
269}
270
271impl Question {
272 pub fn is_unknown(&self) -> bool {
273 self.qtype.is_unknown() || self.qclass.is_unknown()
274 }
275}
276
277impl fmt::Display for Question {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 write!(
280 f,
281 "{} {} {}",
282 self.name.to_dotted_string(),
283 self.qclass,
284 self.qtype
285 )
286 }
287}
288
289#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
318#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
319pub struct ResourceRecord {
320 pub name: DomainName,
322
323 pub rtype_with_data: RecordTypeWithData,
325
326 pub rclass: RecordClass,
329
330 pub ttl: u32,
336}
337
338impl ResourceRecord {
339 pub fn is_unknown(&self) -> bool {
340 self.rtype_with_data.is_unknown() || self.rclass.is_unknown()
341 }
342
343 pub fn matches(&self, question: &Question) -> bool {
344 self.rtype_with_data.matches(question.qtype) && self.rclass.matches(question.qclass)
345 }
346}
347
348#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
350pub enum RecordTypeWithData {
351 A { address: Ipv4Addr },
359
360 NS { nsdname: DomainName },
370
371 MD { madname: DomainName },
382
383 MF { madname: DomainName },
394
395 CNAME { cname: DomainName },
405
406 SOA {
456 mname: DomainName,
457 rname: DomainName,
458 serial: u32,
459 refresh: u32,
460 retry: u32,
461 expire: u32,
462 minimum: u32,
463 },
464
465 MB { madname: DomainName },
475
476 MG { mdmname: DomainName },
487
488 MR { newname: DomainName },
498
499 NULL { octets: Bytes },
509
510 WKS { octets: Bytes },
512
513 PTR { ptrdname: DomainName },
522
523 HINFO { octets: Bytes },
525
526 MINFO {
546 rmailbx: DomainName,
547 emailbx: DomainName,
548 },
549
550 MX {
566 preference: u16,
567 exchange: DomainName,
568 },
569
570 TXT { octets: Bytes },
578
579 AAAA { address: Ipv6Addr },
587
588 SRV {
616 priority: u16,
617 weight: u16,
618 port: u16,
619 target: DomainName,
620 },
621
622 Unknown {
624 tag: RecordTypeUnknown,
625 octets: Bytes,
626 },
627}
628
629impl RecordTypeWithData {
630 pub fn is_unknown(&self) -> bool {
631 self.rtype().is_unknown()
632 }
633
634 pub fn matches(&self, qtype: QueryType) -> bool {
635 self.rtype().matches(qtype)
636 }
637
638 pub fn rtype(&self) -> RecordType {
639 match self {
640 RecordTypeWithData::A { .. } => RecordType::A,
641 RecordTypeWithData::NS { .. } => RecordType::NS,
642 RecordTypeWithData::MD { .. } => RecordType::MD,
643 RecordTypeWithData::MF { .. } => RecordType::MF,
644 RecordTypeWithData::CNAME { .. } => RecordType::CNAME,
645 RecordTypeWithData::SOA { .. } => RecordType::SOA,
646 RecordTypeWithData::MB { .. } => RecordType::MB,
647 RecordTypeWithData::MG { .. } => RecordType::MG,
648 RecordTypeWithData::MR { .. } => RecordType::MR,
649 RecordTypeWithData::NULL { .. } => RecordType::NULL,
650 RecordTypeWithData::WKS { .. } => RecordType::WKS,
651 RecordTypeWithData::PTR { .. } => RecordType::PTR,
652 RecordTypeWithData::HINFO { .. } => RecordType::HINFO,
653 RecordTypeWithData::MINFO { .. } => RecordType::MINFO,
654 RecordTypeWithData::MX { .. } => RecordType::MX,
655 RecordTypeWithData::TXT { .. } => RecordType::TXT,
656 RecordTypeWithData::AAAA { .. } => RecordType::AAAA,
657 RecordTypeWithData::SRV { .. } => RecordType::SRV,
658 RecordTypeWithData::Unknown { tag, .. } => RecordType::Unknown(*tag),
659 }
660 }
661}
662
663#[cfg(any(feature = "test-util", test))]
664impl<'a> arbitrary::Arbitrary<'a> for RecordTypeWithData {
665 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
668 let len = u.int_in_range(0..=128)?;
669 let octets = Bytes::copy_from_slice(u.bytes(len)?);
670
671 let rtype_with_data = match u.arbitrary::<RecordType>()? {
672 RecordType::A => RecordTypeWithData::A {
673 address: u.arbitrary()?,
674 },
675 RecordType::NS => RecordTypeWithData::NS {
676 nsdname: u.arbitrary()?,
677 },
678 RecordType::MD => RecordTypeWithData::MD {
679 madname: u.arbitrary()?,
680 },
681 RecordType::MF => RecordTypeWithData::MF {
682 madname: u.arbitrary()?,
683 },
684 RecordType::CNAME => RecordTypeWithData::CNAME {
685 cname: u.arbitrary()?,
686 },
687 RecordType::SOA => RecordTypeWithData::SOA {
688 mname: u.arbitrary()?,
689 rname: u.arbitrary()?,
690 serial: u.arbitrary()?,
691 refresh: u.arbitrary()?,
692 retry: u.arbitrary()?,
693 expire: u.arbitrary()?,
694 minimum: u.arbitrary()?,
695 },
696 RecordType::MB => RecordTypeWithData::MB {
697 madname: u.arbitrary()?,
698 },
699 RecordType::MG => RecordTypeWithData::MG {
700 mdmname: u.arbitrary()?,
701 },
702 RecordType::MR => RecordTypeWithData::MR {
703 newname: u.arbitrary()?,
704 },
705 RecordType::NULL => RecordTypeWithData::NULL { octets },
706 RecordType::WKS => RecordTypeWithData::WKS { octets },
707 RecordType::PTR => RecordTypeWithData::PTR {
708 ptrdname: u.arbitrary()?,
709 },
710 RecordType::HINFO => RecordTypeWithData::HINFO { octets },
711 RecordType::MINFO => RecordTypeWithData::MINFO {
712 rmailbx: u.arbitrary()?,
713 emailbx: u.arbitrary()?,
714 },
715 RecordType::MX => RecordTypeWithData::MX {
716 preference: u.arbitrary()?,
717 exchange: u.arbitrary()?,
718 },
719 RecordType::TXT => RecordTypeWithData::TXT { octets },
720 RecordType::AAAA => RecordTypeWithData::AAAA {
721 address: u.arbitrary()?,
722 },
723 RecordType::SRV => RecordTypeWithData::SRV {
724 priority: u.arbitrary()?,
725 weight: u.arbitrary()?,
726 port: u.arbitrary()?,
727 target: u.arbitrary()?,
728 },
729 RecordType::Unknown(tag) => RecordTypeWithData::Unknown { tag, octets },
730 };
731 Ok(rtype_with_data)
732 }
733}
734
735#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
737pub enum Opcode {
738 Standard,
739 Inverse,
740 Status,
741 Reserved(OpcodeReserved),
742}
743
744#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
747pub struct OpcodeReserved(u8);
748
749impl Opcode {
750 pub fn is_reserved(&self) -> bool {
751 matches!(self, Opcode::Reserved(_))
752 }
753}
754
755impl From<u8> for Opcode {
756 fn from(octet: u8) -> Self {
757 match octet & 0b0000_1111 {
758 0 => Opcode::Standard,
759 1 => Opcode::Inverse,
760 2 => Opcode::Status,
761 other => Opcode::Reserved(OpcodeReserved(other)),
762 }
763 }
764}
765
766impl From<Opcode> for u8 {
767 fn from(value: Opcode) -> Self {
768 match value {
769 Opcode::Standard => 0,
770 Opcode::Inverse => 1,
771 Opcode::Status => 2,
772 Opcode::Reserved(OpcodeReserved(octet)) => octet,
773 }
774 }
775}
776
777#[cfg(any(feature = "test-util", test))]
778impl<'a> arbitrary::Arbitrary<'a> for Opcode {
779 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
780 Ok(Self::from(u.arbitrary::<u8>()?))
781 }
782}
783
784#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
786pub enum Rcode {
787 NoError,
788 FormatError,
789 ServerFailure,
790 NameError,
791 NotImplemented,
792 Refused,
793 Reserved(RcodeReserved),
794}
795
796#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
799pub struct RcodeReserved(u8);
800
801impl Rcode {
802 pub fn is_reserved(&self) -> bool {
803 matches!(self, Rcode::Reserved(_))
804 }
805}
806
807impl fmt::Display for Rcode {
808 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
809 match self {
810 Rcode::NoError => write!(f, "no-error"),
811 Rcode::FormatError => write!(f, "format-error"),
812 Rcode::ServerFailure => write!(f, "server-failure"),
813 Rcode::NameError => write!(f, "name-error"),
814 Rcode::NotImplemented => write!(f, "not-implemented"),
815 Rcode::Refused => write!(f, "refused"),
816 Rcode::Reserved(_) => write!(f, "reserved"),
817 }
818 }
819}
820
821impl From<u8> for Rcode {
822 fn from(octet: u8) -> Self {
823 match octet & 0b0000_1111 {
824 0 => Rcode::NoError,
825 1 => Rcode::FormatError,
826 2 => Rcode::ServerFailure,
827 3 => Rcode::NameError,
828 4 => Rcode::NotImplemented,
829 5 => Rcode::Refused,
830 other => Rcode::Reserved(RcodeReserved(other)),
831 }
832 }
833}
834
835impl From<Rcode> for u8 {
836 fn from(value: Rcode) -> Self {
837 match value {
838 Rcode::NoError => 0,
839 Rcode::FormatError => 1,
840 Rcode::ServerFailure => 2,
841 Rcode::NameError => 3,
842 Rcode::NotImplemented => 4,
843 Rcode::Refused => 5,
844 Rcode::Reserved(RcodeReserved(octet)) => octet,
845 }
846 }
847}
848
849#[cfg(any(feature = "test-util", test))]
850impl<'a> arbitrary::Arbitrary<'a> for Rcode {
851 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
852 Ok(Self::from(u.arbitrary::<u8>()?))
853 }
854}
855
856#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
862pub struct DomainName {
863 pub labels: Vec<Label>,
864 pub len: usize,
866}
867
868impl DomainName {
869 pub fn root_domain() -> Self {
870 DomainName {
871 labels: vec![Label::new()],
872 len: 1,
873 }
874 }
875
876 pub fn is_root(&self) -> bool {
877 self.len == 1 && self.labels[0].is_empty()
878 }
879
880 pub fn is_subdomain_of(&self, other: &DomainName) -> bool {
881 self.labels.ends_with(&other.labels)
882 }
883
884 pub fn make_subdomain_of(&self, origin: &Self) -> Option<Self> {
885 let mut labels = self.labels.clone();
886 labels.pop();
887 labels.append(&mut origin.labels.clone());
888 DomainName::from_labels(labels)
889 }
890
891 pub fn to_dotted_string(&self) -> String {
892 if self.is_root() {
893 return ".".to_string();
894 }
895
896 let mut out = String::with_capacity(self.len);
897 let mut first = true;
898 for label in &self.labels {
899 if first {
900 first = false;
901 } else {
902 out.push('.');
903 }
904 for octet in &label.octets {
905 out.push(*octet as char);
906 }
907 }
908
909 out
910 }
911
912 pub fn from_relative_dotted_string(origin: &Self, s: &str) -> Option<Self> {
913 if s.is_empty() {
914 Some(origin.clone())
915 } else if s.to_string().ends_with('.') {
916 Self::from_dotted_string(s)
917 } else {
918 let suffix = origin.to_dotted_string();
919 if suffix.starts_with('.') {
920 Self::from_dotted_string(&format!("{s}{suffix}"))
921 } else {
922 Self::from_dotted_string(&format!("{s}.{suffix}"))
923 }
924 }
925 }
926
927 pub fn from_dotted_string(s: &str) -> Option<Self> {
928 if s == "." {
929 return Some(Self::root_domain());
930 }
931
932 let chunks = s.split('.').collect::<Vec<_>>();
933 let mut labels = Vec::with_capacity(chunks.len());
934
935 for (i, label_chars) in chunks.iter().enumerate() {
936 if label_chars.is_empty() && i != chunks.len() - 1 {
937 return None;
938 }
939
940 match label_chars.as_bytes().try_into() {
941 Ok(label) => labels.push(label),
942 Err(_) => return None,
943 }
944 }
945
946 Self::from_labels(labels)
947 }
948
949 pub fn from_labels(labels: Vec<Label>) -> Option<Self> {
950 if labels.is_empty() {
951 return None;
952 }
953
954 let mut len = labels.len();
955 let mut blank_label = false;
956
957 for label in &labels {
958 if blank_label {
959 return None;
960 }
961
962 blank_label |= label.is_empty();
963 len += label.len() as usize;
964 }
965
966 if blank_label && len <= DOMAINNAME_MAX_LEN {
967 Some(Self { labels, len })
968 } else {
969 None
970 }
971 }
972}
973
974impl fmt::Debug for DomainName {
975 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
976 f.debug_struct("DomainName")
977 .field("to_dotted_string()", &self.to_dotted_string())
978 .finish()
979 }
980}
981
982impl fmt::Display for DomainName {
983 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
984 write!(f, "{}", &self.to_dotted_string())
985 }
986}
987
988impl FromStr for DomainName {
989 type Err = DomainNameFromStr;
990
991 fn from_str(s: &str) -> Result<Self, Self::Err> {
992 if let Some(domain) = DomainName::from_dotted_string(s) {
993 Ok(domain)
994 } else {
995 Err(DomainNameFromStr::NoParse)
996 }
997 }
998}
999
1000#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1002pub enum DomainNameFromStr {
1003 NoParse,
1004}
1005
1006impl fmt::Display for DomainNameFromStr {
1007 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1008 write!(f, "could not parse string to domain name")
1009 }
1010}
1011
1012impl std::error::Error for DomainNameFromStr {
1013 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1014 None
1015 }
1016}
1017
1018#[cfg(any(feature = "test-util", test))]
1019impl<'a> arbitrary::Arbitrary<'a> for DomainName {
1020 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1021 let num_labels = u.int_in_range::<usize>(0..=10)?;
1022 let mut labels = Vec::new();
1023 for _ in 0..num_labels {
1024 labels.push(u.arbitrary()?);
1025 }
1026 labels.push(Label::new());
1027 Ok(DomainName::from_labels(labels).unwrap())
1028 }
1029}
1030
1031#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1034pub struct Label {
1035 octets: Bytes,
1038}
1039
1040impl Label {
1041 pub fn new() -> Self {
1043 Self {
1044 octets: Bytes::new(),
1045 }
1046 }
1047
1048 #[allow(clippy::missing_panics_doc)]
1049 pub fn len(&self) -> u8 {
1050 self.octets.len().try_into().unwrap()
1052 }
1053
1054 pub fn is_empty(&self) -> bool {
1055 self.octets.is_empty()
1056 }
1057
1058 pub fn octets(&self) -> &Bytes {
1059 &self.octets
1060 }
1061}
1062
1063impl Default for Label {
1064 fn default() -> Self {
1065 Self::new()
1066 }
1067}
1068
1069impl TryFrom<&[u8]> for Label {
1070 type Error = LabelTryFromOctetsError;
1071
1072 fn try_from(mixed_case_octets: &[u8]) -> Result<Self, Self::Error> {
1073 if mixed_case_octets.len() > LABEL_MAX_LEN {
1074 return Err(LabelTryFromOctetsError::TooLong);
1075 }
1076
1077 Ok(Self {
1078 octets: Bytes::copy_from_slice(&mixed_case_octets.to_ascii_lowercase()),
1079 })
1080 }
1081}
1082
1083#[cfg(any(feature = "test-util", test))]
1084impl<'a> arbitrary::Arbitrary<'a> for Label {
1085 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Label> {
1087 let label_len = u.int_in_range::<u8>(1..=20)?;
1088 let mut octets = BytesMut::with_capacity(label_len.into());
1089 let bs = u.bytes(label_len.into())?;
1090 for b in bs {
1091 let ascii_byte = if b.is_ascii() { *b } else { *b % 128 };
1092 octets.put_u8(
1093 if ascii_byte == b'.'
1094 || ascii_byte == b'*'
1095 || ascii_byte == b'@'
1096 || ascii_byte == b'#'
1097 || (ascii_byte as char).is_whitespace()
1098 {
1099 b'x'
1100 } else {
1101 ascii_byte.to_ascii_lowercase()
1102 },
1103 );
1104 }
1105 Ok(Self {
1106 octets: octets.freeze(),
1107 })
1108 }
1109}
1110
1111#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1113pub enum LabelTryFromOctetsError {
1114 TooLong,
1115}
1116
1117#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1119pub enum QueryType {
1120 Record(RecordType),
1121 AXFR,
1122 MAILB,
1123 MAILA,
1124 Wildcard,
1125}
1126
1127impl QueryType {
1128 pub fn is_unknown(&self) -> bool {
1129 match self {
1130 QueryType::Record(rtype) => rtype.is_unknown(),
1131 _ => false,
1132 }
1133 }
1134}
1135
1136impl fmt::Display for QueryType {
1137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1138 match self {
1139 QueryType::Record(rtype) => rtype.fmt(f),
1140 QueryType::AXFR => write!(f, "AXFR"),
1141 QueryType::MAILA => write!(f, "MAILA"),
1142 QueryType::MAILB => write!(f, "MAILB"),
1143 QueryType::Wildcard => write!(f, "ANY"),
1144 }
1145 }
1146}
1147
1148impl FromStr for QueryType {
1149 type Err = RecordTypeFromStr;
1150
1151 fn from_str(s: &str) -> Result<Self, Self::Err> {
1152 match s {
1153 "AXFR" => Ok(QueryType::AXFR),
1154 "MAILA" => Ok(QueryType::MAILA),
1155 "MAILB" => Ok(QueryType::MAILB),
1156 "ANY" => Ok(QueryType::Wildcard),
1157 _ => RecordType::from_str(s).map(QueryType::Record),
1158 }
1159 }
1160}
1161
1162impl From<u16> for QueryType {
1163 fn from(value: u16) -> Self {
1164 match value {
1165 252 => QueryType::AXFR,
1166 253 => QueryType::MAILB,
1167 254 => QueryType::MAILA,
1168 255 => QueryType::Wildcard,
1169 _ => QueryType::Record(RecordType::from(value)),
1170 }
1171 }
1172}
1173
1174impl From<QueryType> for u16 {
1175 fn from(value: QueryType) -> Self {
1176 match value {
1177 QueryType::AXFR => 252,
1178 QueryType::MAILB => 253,
1179 QueryType::MAILA => 254,
1180 QueryType::Wildcard => 255,
1181 QueryType::Record(rtype) => rtype.into(),
1182 }
1183 }
1184}
1185
1186#[cfg(any(feature = "test-util", test))]
1187impl<'a> arbitrary::Arbitrary<'a> for QueryType {
1188 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1189 Ok(Self::from(u.arbitrary::<u16>()?))
1190 }
1191}
1192
1193#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1195pub enum QueryClass {
1196 Record(RecordClass),
1197 Wildcard,
1198}
1199
1200impl QueryClass {
1201 pub fn is_unknown(&self) -> bool {
1202 match self {
1203 QueryClass::Record(rclass) => rclass.is_unknown(),
1204 QueryClass::Wildcard => false,
1205 }
1206 }
1207}
1208
1209impl fmt::Display for QueryClass {
1210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1211 match self {
1212 QueryClass::Record(rclass) => rclass.fmt(f),
1213 QueryClass::Wildcard => write!(f, "ANY"),
1214 }
1215 }
1216}
1217
1218impl FromStr for QueryClass {
1219 type Err = RecordClassFromStr;
1220
1221 fn from_str(s: &str) -> Result<Self, Self::Err> {
1222 match s {
1223 "ANY" => Ok(QueryClass::Wildcard),
1224 _ => RecordClass::from_str(s).map(QueryClass::Record),
1225 }
1226 }
1227}
1228
1229impl From<u16> for QueryClass {
1230 fn from(value: u16) -> Self {
1231 match value {
1232 255 => QueryClass::Wildcard,
1233 _ => QueryClass::Record(RecordClass::from(value)),
1234 }
1235 }
1236}
1237
1238impl From<QueryClass> for u16 {
1239 fn from(value: QueryClass) -> Self {
1240 match value {
1241 QueryClass::Wildcard => 255,
1242 QueryClass::Record(rclass) => rclass.into(),
1243 }
1244 }
1245}
1246
1247#[cfg(any(feature = "test-util", test))]
1248impl<'a> arbitrary::Arbitrary<'a> for QueryClass {
1249 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1250 Ok(Self::from(u.arbitrary::<u16>()?))
1251 }
1252}
1253
1254#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1256pub enum RecordType {
1257 A,
1258 NS,
1259 MD,
1260 MF,
1261 CNAME,
1262 SOA,
1263 MB,
1264 MG,
1265 MR,
1266 NULL,
1267 WKS,
1268 PTR,
1269 HINFO,
1270 MINFO,
1271 MX,
1272 TXT,
1273 AAAA,
1274 SRV,
1275 Unknown(RecordTypeUnknown),
1276}
1277
1278#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1281pub struct RecordTypeUnknown(u16);
1282
1283impl RecordType {
1284 pub fn is_unknown(&self) -> bool {
1285 matches!(self, RecordType::Unknown(_))
1286 }
1287
1288 pub fn matches(&self, qtype: QueryType) -> bool {
1289 match qtype {
1290 QueryType::Wildcard => true,
1291 QueryType::Record(rtype) => rtype == *self,
1292 _ => false,
1293 }
1294 }
1295}
1296
1297impl fmt::Display for RecordType {
1298 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1299 match self {
1300 RecordType::A => write!(f, "A"),
1301 RecordType::NS => write!(f, "NS"),
1302 RecordType::MD => write!(f, "MD"),
1303 RecordType::MF => write!(f, "MF"),
1304 RecordType::CNAME => write!(f, "CNAME"),
1305 RecordType::SOA => write!(f, "SOA"),
1306 RecordType::MB => write!(f, "MB"),
1307 RecordType::MG => write!(f, "MG"),
1308 RecordType::MR => write!(f, "MR"),
1309 RecordType::NULL => write!(f, "NULL"),
1310 RecordType::WKS => write!(f, "WKS"),
1311 RecordType::PTR => write!(f, "PTR"),
1312 RecordType::HINFO => write!(f, "HINFO"),
1313 RecordType::MINFO => write!(f, "MINFO"),
1314 RecordType::MX => write!(f, "MX"),
1315 RecordType::TXT => write!(f, "TXT"),
1316 RecordType::AAAA => write!(f, "AAAA"),
1317 RecordType::SRV => write!(f, "SRV"),
1318 RecordType::Unknown(RecordTypeUnknown(n)) => write!(f, "TYPE{n}"),
1319 }
1320 }
1321}
1322
1323impl FromStr for RecordType {
1324 type Err = RecordTypeFromStr;
1325
1326 fn from_str(s: &str) -> Result<Self, Self::Err> {
1327 match s {
1328 "A" => Ok(RecordType::A),
1329 "NS" => Ok(RecordType::NS),
1330 "MD" => Ok(RecordType::MD),
1331 "MF" => Ok(RecordType::MF),
1332 "CNAME" => Ok(RecordType::CNAME),
1333 "SOA" => Ok(RecordType::SOA),
1334 "MB" => Ok(RecordType::MB),
1335 "MG" => Ok(RecordType::MG),
1336 "MR" => Ok(RecordType::MR),
1337 "NULL" => Ok(RecordType::NULL),
1338 "WKS" => Ok(RecordType::WKS),
1339 "PTR" => Ok(RecordType::PTR),
1340 "HINFO" => Ok(RecordType::HINFO),
1341 "MINFO" => Ok(RecordType::MINFO),
1342 "MX" => Ok(RecordType::MX),
1343 "TXT" => Ok(RecordType::TXT),
1344 "AAAA" => Ok(RecordType::AAAA),
1345 "SRV" => Ok(RecordType::SRV),
1346 _ => {
1347 if let Some(type_str) = s.strip_prefix("TYPE") {
1348 if let Ok(type_num) = u16::from_str(type_str) {
1349 Ok(RecordType::from(type_num))
1350 } else {
1351 Err(RecordTypeFromStr::BadType)
1352 }
1353 } else {
1354 Err(RecordTypeFromStr::NoParse)
1355 }
1356 }
1357 }
1358 }
1359}
1360
1361#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1363pub enum RecordTypeFromStr {
1364 BadType,
1365 NoParse,
1366}
1367
1368impl fmt::Display for RecordTypeFromStr {
1369 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1370 match self {
1371 RecordTypeFromStr::BadType => write!(f, "TYPE<num> number must be a u16"),
1372 RecordTypeFromStr::NoParse => write!(f, "could not parse string to type"),
1373 }
1374 }
1375}
1376
1377impl std::error::Error for RecordTypeFromStr {
1378 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1379 None
1380 }
1381}
1382
1383impl From<u16> for RecordType {
1384 fn from(value: u16) -> Self {
1385 match value {
1386 1 => RecordType::A,
1387 2 => RecordType::NS,
1388 3 => RecordType::MD,
1389 4 => RecordType::MF,
1390 5 => RecordType::CNAME,
1391 6 => RecordType::SOA,
1392 7 => RecordType::MB,
1393 8 => RecordType::MG,
1394 9 => RecordType::MR,
1395 10 => RecordType::NULL,
1396 11 => RecordType::WKS,
1397 12 => RecordType::PTR,
1398 13 => RecordType::HINFO,
1399 14 => RecordType::MINFO,
1400 15 => RecordType::MX,
1401 16 => RecordType::TXT,
1402 28 => RecordType::AAAA,
1403 33 => RecordType::SRV,
1404 _ => RecordType::Unknown(RecordTypeUnknown(value)),
1405 }
1406 }
1407}
1408
1409impl From<RecordType> for u16 {
1410 fn from(value: RecordType) -> Self {
1411 match value {
1412 RecordType::A => 1,
1413 RecordType::NS => 2,
1414 RecordType::MD => 3,
1415 RecordType::MF => 4,
1416 RecordType::CNAME => 5,
1417 RecordType::SOA => 6,
1418 RecordType::MB => 7,
1419 RecordType::MG => 8,
1420 RecordType::MR => 9,
1421 RecordType::NULL => 10,
1422 RecordType::WKS => 11,
1423 RecordType::PTR => 12,
1424 RecordType::HINFO => 13,
1425 RecordType::MINFO => 14,
1426 RecordType::MX => 15,
1427 RecordType::TXT => 16,
1428 RecordType::AAAA => 28,
1429 RecordType::SRV => 33,
1430 RecordType::Unknown(RecordTypeUnknown(value)) => value,
1431 }
1432 }
1433}
1434
1435#[cfg(any(feature = "test-util", test))]
1436impl<'a> arbitrary::Arbitrary<'a> for RecordType {
1437 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1438 Ok(Self::from(u.arbitrary::<u16>()?))
1439 }
1440}
1441
1442#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1444pub enum RecordClass {
1445 IN,
1446 Unknown(RecordClassUnknown),
1447}
1448
1449#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1452pub struct RecordClassUnknown(u16);
1453
1454impl RecordClass {
1455 pub fn is_unknown(&self) -> bool {
1456 matches!(self, RecordClass::Unknown(_))
1457 }
1458
1459 pub fn matches(&self, qclass: QueryClass) -> bool {
1460 match qclass {
1461 QueryClass::Wildcard => true,
1462 QueryClass::Record(rclass) => rclass == *self,
1463 }
1464 }
1465}
1466
1467impl fmt::Display for RecordClass {
1468 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1469 match self {
1470 RecordClass::IN => write!(f, "IN"),
1471 RecordClass::Unknown(RecordClassUnknown(n)) => write!(f, "CLASS{n}"),
1472 }
1473 }
1474}
1475
1476impl FromStr for RecordClass {
1477 type Err = RecordClassFromStr;
1478
1479 fn from_str(s: &str) -> Result<Self, Self::Err> {
1480 match s {
1481 "IN" => Ok(RecordClass::IN),
1482 _ => {
1483 if let Some(class_str) = s.strip_prefix("CLASS") {
1484 if let Ok(class_num) = u16::from_str(class_str) {
1485 Ok(RecordClass::from(class_num))
1486 } else {
1487 Err(RecordClassFromStr::BadClass)
1488 }
1489 } else {
1490 Err(RecordClassFromStr::NoParse)
1491 }
1492 }
1493 }
1494 }
1495}
1496
1497#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1499pub enum RecordClassFromStr {
1500 BadClass,
1501 NoParse,
1502}
1503
1504impl fmt::Display for RecordClassFromStr {
1505 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1506 match self {
1507 RecordClassFromStr::BadClass => write!(f, "CLASS<num> number must be a u16"),
1508 RecordClassFromStr::NoParse => write!(f, "could not parse string to class"),
1509 }
1510 }
1511}
1512
1513impl std::error::Error for RecordClassFromStr {
1514 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1515 None
1516 }
1517}
1518
1519impl From<u16> for RecordClass {
1520 fn from(value: u16) -> Self {
1521 match value {
1522 1 => RecordClass::IN,
1523 _ => RecordClass::Unknown(RecordClassUnknown(value)),
1524 }
1525 }
1526}
1527
1528impl From<RecordClass> for u16 {
1529 fn from(value: RecordClass) -> Self {
1530 match value {
1531 RecordClass::IN => 1,
1532 RecordClass::Unknown(RecordClassUnknown(value)) => value,
1533 }
1534 }
1535}
1536
1537#[cfg(any(feature = "test-util", test))]
1538impl<'a> arbitrary::Arbitrary<'a> for RecordClass {
1539 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1540 Ok(Self::from(u.arbitrary::<u16>()?))
1541 }
1542}
1543
1544#[cfg(test)]
1545mod tests {
1546 use rand::Rng;
1547
1548 use super::test_util::*;
1549 use super::*;
1550
1551 #[test]
1552 fn u8_opcode_roundtrip() {
1553 for i in 0..15 {
1554 assert_eq!(u8::from(Opcode::from(i)), i);
1555 }
1556 }
1557
1558 #[test]
1559 fn u8_rcode_roundtrip() {
1560 for i in 0..15 {
1561 assert_eq!(u8::from(Rcode::from(i)), i);
1562 }
1563 }
1564
1565 #[test]
1566 fn u16_querytype_roundtrip() {
1567 for i in 0..100 {
1568 assert_eq!(u16::from(QueryType::from(i)), i);
1569 }
1570 }
1571
1572 #[test]
1573 fn u16_queryclass_roundtrip() {
1574 for i in 0..100 {
1575 assert_eq!(u16::from(QueryClass::from(i)), i);
1576 }
1577 }
1578
1579 #[test]
1580 fn u16_recordtype_roundtrip() {
1581 for i in 0..100 {
1582 assert_eq!(u16::from(RecordType::from(i)), i);
1583 }
1584 }
1585
1586 #[test]
1587 fn recordtype_unknown_implies_querytype_unknown() {
1588 for i in 0..100 {
1589 if RecordType::from(i).is_unknown() {
1590 assert!(QueryType::from(i).is_unknown());
1591 }
1592 }
1593 }
1594
1595 #[test]
1596 fn u16_recordclass_roundtrip() {
1597 for i in 0..100 {
1598 assert_eq!(u16::from(RecordClass::from(i)), i);
1599 }
1600 }
1601
1602 #[test]
1603 fn recordclass_unknown_implies_queryclass_unknown() {
1604 for i in 0..100 {
1605 if RecordClass::from(i).is_unknown() {
1606 assert!(QueryClass::from(i).is_unknown());
1607 }
1608 }
1609 }
1610
1611 #[test]
1612 fn domainname_root_conversions() {
1613 assert_eq!(
1614 Some(DomainName::root_domain()),
1615 DomainName::from_dotted_string(".")
1616 );
1617
1618 assert_eq!(
1619 Some(DomainName::root_domain()),
1620 DomainName::from_labels(vec![Label::new()])
1621 );
1622
1623 assert_eq!(".", DomainName::root_domain().to_dotted_string());
1624 }
1625
1626 #[test]
1627 fn from_relative_dotted_string_empty() {
1628 let origin = domain("com.");
1629 assert_eq!(
1630 Some(domain("com.")),
1631 DomainName::from_relative_dotted_string(&origin, "")
1632 );
1633 }
1634
1635 #[test]
1636 fn from_relative_dotted_string_absolute() {
1637 let origin = domain("com.");
1638 assert_eq!(
1639 Some(domain("www.example.com.")),
1640 DomainName::from_relative_dotted_string(&origin, "www.example.com.")
1641 );
1642 }
1643
1644 #[test]
1645 fn from_relative_dotted_string_relative() {
1646 let origin = domain("com.");
1647 assert_eq!(
1648 Some(domain("www.example.com.")),
1649 DomainName::from_relative_dotted_string(&origin, "www.example")
1650 );
1651 }
1652
1653 #[test]
1654 fn make_subdomain_is_subdomain() {
1655 let sub = domain("foo.");
1656 let apex = domain("bar.");
1657 let combined = sub.make_subdomain_of(&apex);
1658
1659 assert_eq!(Some(domain("foo.bar.")), combined);
1660 assert!(combined.unwrap().is_subdomain_of(&apex));
1661 }
1662
1663 #[test]
1664 fn domainname_conversions() {
1665 let mut rng = rand::rng();
1666 for _ in 0..100 {
1667 let labels_len = rng.random_range(0..5);
1668
1669 let mut dotted_string_input = String::new();
1670 let mut labels_input = Vec::with_capacity(labels_len);
1671 let mut output = String::new();
1672
1673 for i in 0..labels_len {
1674 let label_len = rng.random_range(1..10);
1675
1676 if i > 0 {
1677 dotted_string_input.push('.');
1678 output.push('.');
1679 }
1680
1681 let mut octets = BytesMut::with_capacity(label_len);
1682 for _ in 0..label_len {
1683 let mut chr = rng.random_range(32..126);
1684
1685 if chr == b'.'
1686 || chr == b'*'
1687 || chr == b'@'
1688 || chr == b'#'
1689 || (chr as char).is_whitespace()
1690 {
1691 chr = b'X';
1692 }
1693
1694 octets.put_u8(chr);
1695 dotted_string_input.push(chr as char);
1696 output.push(chr.to_ascii_lowercase() as char);
1697 }
1698 labels_input.push(Label::try_from(&octets.freeze()[..]).unwrap());
1699 }
1700
1701 labels_input.push(Label::new());
1702 dotted_string_input.push('.');
1703 output.push('.');
1704
1705 assert_eq!(
1706 Some(output.clone()),
1707 DomainName::from_dotted_string(&dotted_string_input).map(|d| d.to_dotted_string())
1708 );
1709
1710 assert_eq!(
1711 Some(output),
1712 DomainName::from_labels(labels_input.clone()).map(|d| d.to_dotted_string())
1713 );
1714
1715 assert_eq!(
1716 DomainName::from_dotted_string(&dotted_string_input).map(|d| d.to_dotted_string()),
1717 DomainName::from_labels(labels_input).map(|d| d.to_dotted_string())
1718 );
1719 }
1720 }
1721}
1722
1723#[cfg(any(feature = "test-util", test))]
1724#[allow(clippy::missing_panics_doc)]
1725pub mod test_util {
1726 use super::*;
1727
1728 use arbitrary::{Arbitrary, Unstructured};
1729 use rand::Rng;
1730
1731 pub fn arbitrary_resourcerecord() -> ResourceRecord {
1732 let mut rng = rand::rng();
1733 for size in [128, 256, 512, 1024, 2048, 4096] {
1734 let mut buf = BytesMut::with_capacity(size);
1735 for _ in 0..size {
1736 buf.put_u8(rng.random());
1737 }
1738
1739 if let Ok(rr) = ResourceRecord::arbitrary(&mut Unstructured::new(&buf.freeze())) {
1740 return rr;
1741 }
1742 }
1743
1744 panic!("could not generate arbitrary value!");
1745 }
1746
1747 pub fn domain(name: &str) -> DomainName {
1748 DomainName::from_dotted_string(name).unwrap()
1749 }
1750
1751 pub fn a_record(name: &str, address: Ipv4Addr) -> ResourceRecord {
1752 ResourceRecord {
1753 name: domain(name),
1754 rtype_with_data: RecordTypeWithData::A { address },
1755 rclass: RecordClass::IN,
1756 ttl: 300,
1757 }
1758 }
1759
1760 pub fn aaaa_record(name: &str, address: Ipv6Addr) -> ResourceRecord {
1761 ResourceRecord {
1762 name: domain(name),
1763 rtype_with_data: RecordTypeWithData::AAAA { address },
1764 rclass: RecordClass::IN,
1765 ttl: 300,
1766 }
1767 }
1768
1769 pub fn cname_record(name: &str, target_name: &str) -> ResourceRecord {
1770 ResourceRecord {
1771 name: domain(name),
1772 rtype_with_data: RecordTypeWithData::CNAME {
1773 cname: domain(target_name),
1774 },
1775 rclass: RecordClass::IN,
1776 ttl: 300,
1777 }
1778 }
1779
1780 pub fn ns_record(superdomain_name: &str, nameserver_name: &str) -> ResourceRecord {
1781 ResourceRecord {
1782 name: domain(superdomain_name),
1783 rtype_with_data: RecordTypeWithData::NS {
1784 nsdname: domain(nameserver_name),
1785 },
1786 rclass: RecordClass::IN,
1787 ttl: 300,
1788 }
1789 }
1790
1791 pub fn unknown_record(name: &str, octets: &[u8]) -> ResourceRecord {
1792 ResourceRecord {
1793 name: domain(name),
1794 rtype_with_data: RecordTypeWithData::Unknown {
1795 tag: RecordTypeUnknown(100),
1796 octets: Bytes::copy_from_slice(octets),
1797 },
1798 rclass: RecordClass::IN,
1799 ttl: 300,
1800 }
1801 }
1802}