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