]> git.bitcoin.ninja Git - dnssec-prover/commitdiff
Keep encoding information in `Txt` `RR`s
authorMatt Corallo <git@bluematt.me>
Tue, 9 Jul 2024 20:54:26 +0000 (20:54 +0000)
committerMatt Corallo <git@bluematt.me>
Tue, 9 Jul 2024 21:12:49 +0000 (21:12 +0000)
Sadly `TXT` records can be encoded in many ways (as they're
chunked, and chunks can be any size), and are signed in the way in
which we receive them on the wire. Thus, we must keep the encoding
information we receive on the wire around in `Txt`s to ensure we
can validate their signatures.

Here we do so, creating a pile of new machinery to store `Txt` data
as a series of up-to-255-byte chunks.

src/query.rs
src/rr.rs
src/validation.rs

index fd9982700560bebd297bb5aa7662086b5da7a8b1..e24e52fdfc8562efed94dc821d60824ccfa7b98d 100644 (file)
@@ -522,7 +522,7 @@ mod tests {
                        assert_eq!(resolved_rrs.len(), 1);
                        if let RR::Txt(txt) = &resolved_rrs[0] {
                                assert_eq!(txt.name.as_str(), "txt_test.dnssec_proof_tests.bitcoin.ninja.");
-                               assert_eq!(txt.data, b"dnssec_prover_test");
+                               assert_eq!(txt.data.as_vec(), b"dnssec_prover_test");
                        } else { panic!(); }
                }
        }
@@ -568,7 +568,7 @@ mod tests {
                        assert_eq!(resolved_rrs.len(), 1);
                        if let RR::Txt(txt) = &resolved_rrs[0] {
                                assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
-                               assert!(txt.data.starts_with(b"bitcoin:"));
+                               assert!(txt.data.as_vec().starts_with(b"bitcoin:"));
                        } else { panic!(); }
                }
        }
@@ -594,7 +594,7 @@ mod tests {
                        assert_eq!(resolved_rrs.len(), 1);
                        if let RR::Txt(txt) = &resolved_rrs[0] {
                                assert_eq!(txt.name.as_str(), "cname.wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
-                               assert_eq!(txt.data, b"wildcard_test");
+                               assert_eq!(txt.data.as_vec(), b"wildcard_test");
                        } else { panic!(); }
                }
        }
index a3839d29bf317c71e9260a1331c4e21e6055cfd1..5aa7a5774c7e2f09903b8ab8b7065aa1b62c8242 100644 (file)
--- a/src/rr.rs
+++ b/src/rr.rs
@@ -8,9 +8,10 @@ use alloc::string::String;
 use alloc::borrow::ToOwned;
 use alloc::format;
 
-use core::cmp::{self, Ordering};
+use core::cmp::Ordering;
 use core::fmt;
 use core::fmt::Write;
+use core::num::NonZeroU8;
 
 use crate::ser::*;
 
@@ -259,6 +260,126 @@ impl Record for RR {
        }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct TxtBytePart {
+       /// The bytes themselves.
+       ///
+       /// Bytes at or beyond [`Self::len`] may be filled with garbage and should be ignored.
+       bytes: [u8; 255],
+       /// The number of bytes which are to be used.
+       len: NonZeroU8,
+}
+
+/// The bytes of a [`Txt`] record.
+///
+/// They are stored as a series of byte buffers so that we can reconstruct the exact encoding which
+/// was used for signatures, however they're really just a simple list of bytes, and the underlying
+/// encoding should be ignored.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TxtBytes {
+       /// The series of byte buffers storing the bytes themselves.
+       chunks: Vec<TxtBytePart>,
+}
+
+impl TxtBytes {
+       /// Constructs a new [`TxtBytes`] from the given bytes
+       ///
+       /// Fails if there are too many bytes to fit in a [`Txt`] record.
+       pub fn new(bytes: &[u8]) -> Result<TxtBytes, ()> {
+               if bytes.len() > 255*255 + 254 { return Err(()); }
+               let mut chunks = Vec::with_capacity((bytes.len() + 254) / 255);
+               let mut data_write = &bytes[..];
+               while !data_write.is_empty() {
+                       let split_pos = core::cmp::min(255, data_write.len());
+                       let mut part = TxtBytePart {
+                               bytes: [0; 255],
+                               len: (split_pos as u8).try_into().expect("Cannot be 0 as data_write is not empty"),
+                       };
+                       part.bytes[..split_pos].copy_from_slice(&data_write[..split_pos]);
+                       chunks.push(part);
+                       data_write = &data_write[split_pos..];
+               }
+               debug_assert_eq!(chunks.len(), (bytes.len() + 254) / 255);
+               Ok(TxtBytes { chunks })
+       }
+
+       /// Gets the total number of bytes represented in this record.
+       pub fn len(&self) -> usize {
+               let mut res = 0;
+               for chunk in self.chunks.iter() {
+                       res += chunk.len.get() as usize;
+               }
+               res
+       }
+
+       /// The length of the bytes when serialized on the wire.
+       pub fn serialized_len(&self) -> u16 {
+               let mut len = 0u16;
+               for chunk in self.chunks.iter() {
+                       len = len.checked_add(1 + chunk.len.get() as u16)
+                               .expect("TxtBytes objects must fit in 2^16 - 1 bytes when serialized");
+               }
+               len
+       }
+
+       /// Gets the bytes as a flat `Vec` of `u8`s. This should be considered
+       pub fn as_vec(&self) -> Vec<u8> {
+               let mut res = Vec::with_capacity(self.len());
+               for chunk in self.chunks.iter() {
+                       res.extend_from_slice(&chunk.bytes[..chunk.len.get() as usize]);
+               }
+               res
+       }
+
+       /// Gets an iterator over all the bytes in this [`TxtBytes`].
+       pub fn iter<'a>(&'a self) -> TxtBytesIter<'a> {
+               TxtBytesIter {
+                       bytes: self,
+                       next_part: 0,
+                       next_byte: 0,
+               }
+       }
+}
+
+impl TryFrom<&str> for TxtBytes {
+       type Error = ();
+       fn try_from(s: &str) -> Result<TxtBytes, ()> {
+               TxtBytes::new(s.as_bytes())
+       }
+}
+
+impl TryFrom<&[u8]> for TxtBytes {
+       type Error = ();
+       fn try_from(b: &[u8]) -> Result<TxtBytes, ()> {
+               TxtBytes::new(b)
+       }
+}
+
+/// An iterator over the bytes in a [`TxtBytes`]
+pub struct TxtBytesIter<'a> {
+       bytes: &'a TxtBytes,
+       next_part: usize,
+       next_byte: u8,
+}
+
+impl<'a> Iterator for TxtBytesIter<'a> {
+       type Item = u8;
+       fn next(&mut self) -> Option<u8> {
+               self.bytes.chunks.get(self.next_part)
+                       .and_then(|part| if self.next_byte >= part.len.get() {
+                               None
+                       } else {
+                               if self.next_byte == part.len.get() - 1 {
+                                       self.next_byte = 0;
+                                       self.next_part += 1;
+                               } else {
+                                       self.next_byte += 1;
+                               }
+                               Some(part.bytes[self.next_byte as usize])
+                       })
+       }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq)]
 /// A text resource record, containing arbitrary text data
 pub struct Txt {
@@ -268,7 +389,7 @@ pub struct Txt {
        ///
        /// While this is generally UTF-8-valid, there is no specific requirement that it be, and thus
        /// is an arbitrary series of bytes here.
-       pub data: Vec<u8>,
+       pub data: TxtBytes,
 }
 /// The wire type for TXT records
 pub const TXT_TYPE: u16 = 16;
@@ -276,16 +397,24 @@ impl Ord for Txt {
        fn cmp(&self, o: &Txt) -> Ordering {
                self.name.cmp(&o.name)
                        .then_with(|| {
-                               // Compare in wire encoding form, i.e. compare in 255-byte chunks
-                               for i in 1..(self.data.len() / 255) + 2 {
-                                       let start = (i - 1)*255;
-                                       let self_len = cmp::min(i * 255, self.data.len());
-                                       let o_len = cmp::min(i * 255, o.data.len());
-                                       let slice_cmp = self_len.cmp(&o_len)
-                                               .then_with(|| self.data[start..self_len].cmp(&o.data[start..o_len]));
-                                       if !slice_cmp.is_eq() { return slice_cmp; }
+                               // Compare in wire encoding form, i.e. compare checks in order
+                               let mut o_chunks = o.data.chunks.iter();
+                               for chunk in self.data.chunks.iter() {
+                                       if let Some(o_chunk) = o_chunks.next() {
+                                               let chunk_cmp = chunk.len.cmp(&o_chunk.len)
+                                                       .then_with(||chunk.bytes[..chunk.len.get() as usize]
+                                                               .cmp(&o_chunk.bytes[..o_chunk.len.get() as usize]));
+                                               if !chunk_cmp.is_eq() { return chunk_cmp; }
+                                       } else {
+                                               // self has more chunks than o
+                                               return Ordering::Greater;
+                                       }
+                               }
+                               if o_chunks.next().is_some() {
+                                       Ordering::Less
+                               } else {
+                                       Ordering::Equal
                                }
-                               Ordering::Equal
                        })
        }
 }
@@ -296,37 +425,54 @@ impl StaticRecord for Txt {
        const TYPE: u16 = TXT_TYPE;
        fn name(&self) -> &Name { &self.name }
        fn json(&self) -> String {
-               if let Ok(s) = core::str::from_utf8(&self.data) {
-                       if s.chars().all(|c| !c.is_control() && c != '"' && c != '\\') {
-                               return format!("{{\"type\":\"txt\",\"name\":\"{}\",\"contents\":\"{}\"}}", self.name.0, s);
+               let mut res = format!("{{\"type\":\"txt\",\"name\":\"{}\",\"contents\":", self.name.0);
+               if self.data.iter().all(|b| b >= 0x20 && b <= 0x7e) {
+                       res += "\"";
+                       for b in self.data.iter() {
+                               res.push(b as char);
                        }
+                       res += "\"}";
+               } else {
+                       res += "[";
+                       let mut first_b = true;
+                       for b in self.data.iter() {
+                               if !first_b { res += ","; }
+                               write!(&mut res, "{}", b).expect("Shouldn't fail to write to a String");
+                               first_b = false;
+                       }
+                       res += "]}";
                }
-               format!("{{\"type\":\"txt\",\"name\":\"{}\",\"contents\":{:?}}}", self.name.0, &self.data[..])
+               res
        }
        fn read_from_data(name: Name, mut data: &[u8], _wire_packet: &[u8]) -> Result<Self, ()> {
-               let mut parsed_data = Vec::with_capacity(data.len().saturating_sub(1));
+               let mut parts = TxtBytes {
+                       chunks: Vec::with_capacity((data.len() + 255) / 256),
+               };
+               let mut serialized_len = 0;
                while !data.is_empty() {
-                       let len = read_u8(&mut data)? as usize;
-                       if data.len() < len { return Err(()); }
-                       parsed_data.extend_from_slice(&data[..len]);
-                       data = &data[len..];
+                       let len = read_u8(&mut data)?;
+                       if data.len() < len as usize { return Err(()); }
+                       if len == 0 { return Err(()); }
+                       serialized_len += 1 + len as u32;
+                       if serialized_len > u16::MAX as u32 {
+                               return Err(());
+                       }
+                       let mut part = TxtBytePart {
+                               bytes: [0; 255],
+                               len: len.try_into().expect("We already checked 0 above"),
+                       };
+                       part.bytes[..len as usize].copy_from_slice(&data[..len as usize]);
+                       data = &data[len as usize..];
+                       parts.chunks.push(part);
                }
                debug_assert!(data.is_empty());
-               Ok(Txt { name, data: parsed_data })
+               Ok(Txt { name, data: parts })
        }
        fn write_u16_len_prefixed_data<W: Writer>(&self, out: &mut W) {
-               let len = (self.data.len() + (self.data.len() + 254) / 255) as u16;
-               out.write(&len.to_be_bytes());
-
-               let mut data_write = &self.data[..];
-               out.write(&[data_write.len().try_into().unwrap_or(255)]);
-               while !data_write.is_empty() {
-                       let split_pos = core::cmp::min(255, data_write.len());
-                       out.write(&data_write[..split_pos]);
-                       data_write = &data_write[split_pos..];
-                       if !data_write.is_empty() {
-                               out.write(&[data_write.len().try_into().unwrap_or(255)]);
-                       }
+               out.write(&self.data.serialized_len().to_be_bytes());
+               for chunk in self.data.chunks.iter() {
+                       out.write(&[chunk.len.get()]);
+                       out.write(&chunk.bytes[..chunk.len.get() as usize]);
                }
        }
 }
index 35096512ea52e4bd04f4bbf020f72a022a56cf7d..cbcfcfa499e04cefc8c01292b8f8432a35e07630 100644 (file)
@@ -700,7 +700,7 @@ mod tests {
        fn mattcorallo_txt_record() -> (Txt, RRSig) {
                let txt_resp = Txt {
                        name: "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap(),
-                       data: "bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y".to_owned().into_bytes(),
+                       data: "bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap(),
@@ -744,7 +744,7 @@ mod tests {
        fn bitcoin_ninja_txt_record() -> (Txt, RRSig) {
                let txt_resp = Txt {
                        name: "txt_test.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "dnssec_prover_test".to_owned().into_bytes(),
+                       data: "dnssec_prover_test".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: "txt_test.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
@@ -772,28 +772,28 @@ mod tests {
        fn bitcoin_ninja_txt_sort_edge_cases_records() -> (Vec<Txt>, RRSig) {
                let txts = vec![Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaa".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaa".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".try_into().unwrap(),
                }, Txt {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".to_owned().into_bytes(),
+                       data: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaba".try_into().unwrap(),
                }];
                let rrsig = RRSig {
                        name: "txt_sort_order.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
@@ -809,7 +809,7 @@ mod tests {
                let name: Name = (pfx.to_owned() + ".wildcard_test.dnssec_proof_tests.bitcoin.ninja.").try_into().unwrap();
                let txt_resp = Txt {
                        name: name.clone(),
-                       data: "wildcard_test".to_owned().into_bytes(),
+                       data: "wildcard_test".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: name.clone(),
@@ -910,7 +910,7 @@ mod tests {
        fn bitcoin_ninja_nsec_record() -> (Txt, RRSig) {
                let txt_resp = Txt {
                        name: "a.nsec_tests.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
-                       data: "txt_a".to_owned().into_bytes(),
+                       data: "txt_a".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: "a.nsec_tests.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap(),
@@ -925,7 +925,7 @@ mod tests {
                let name: Name = (pfx.to_owned() + ".wildcard_test.nsec_tests.dnssec_proof_tests.bitcoin.ninja.").try_into().unwrap();
                let txt_resp = Txt {
                        name: name.clone(),
-                       data: "wildcard_test".to_owned().into_bytes(),
+                       data: "wildcard_test".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: name.clone(),
@@ -951,7 +951,7 @@ mod tests {
                let name: Name = (pfx.to_owned() + ".wildcard_test.nsec_tests.dnssec_proof_tests.bitcoin.ninja.").try_into().unwrap();
                let txt_resp = Txt {
                        name: name.clone(),
-                       data: "wildcard_test".to_owned().into_bytes(),
+                       data: "wildcard_test".try_into().unwrap(),
                };
                let txt_rrsig = RRSig {
                        name: name.clone(),
@@ -996,7 +996,7 @@ mod tests {
                assert_eq!(verified_rrs.verified_rrs.len(), 1);
                if let RR::Txt(txt) = &verified_rrs.verified_rrs[0] {
                        assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
-                       assert_eq!(txt.data, b"bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y");
+                       assert_eq!(txt.data.as_vec(), b"bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y");
                } else { panic!(); }
                assert_eq!(verified_rrs.valid_from, 1709047250); // The mattcorallo.com. DNSKEY RRSig was created last
                assert_eq!(verified_rrs.expires, 1709359258); // The mattcorallo.com. DS RRSig expires first
@@ -1041,11 +1041,11 @@ mod tests {
                assert_eq!(verified_rrs.verified_rrs.len(), 3);
                if let RR::Txt(txt) = &verified_rrs.verified_rrs[0] {
                        assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
-                       assert_eq!(txt.data, b"bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y");
+                       assert_eq!(txt.data.as_vec(), b"bitcoin:?b12=lno1qsgqmqvgm96frzdg8m0gc6nzeqffvzsqzrxqy32afmr3jn9ggkwg3egfwch2hy0l6jut6vfd8vpsc3h89l6u3dm4q2d6nuamav3w27xvdmv3lpgklhg7l5teypqz9l53hj7zvuaenh34xqsz2sa967yzqkylfu9xtcd5ymcmfp32h083e805y7jfd236w9afhavqqvl8uyma7x77yun4ehe9pnhu2gekjguexmxpqjcr2j822xr7q34p078gzslf9wpwz5y57alxu99s0z2ql0kfqvwhzycqq45ehh58xnfpuek80hw6spvwrvttjrrq9pphh0dpydh06qqspp5uq4gpyt6n9mwexde44qv7lstzzq60nr40ff38u27un6y53aypmx0p4qruk2tf9mjwqlhxak4znvna5y");
                } else { panic!(); }
                if let RR::Txt(txt) = &verified_rrs.verified_rrs[1] {
                        assert_eq!(txt.name.as_str(), "txt_test.dnssec_proof_tests.bitcoin.ninja.");
-                       assert_eq!(txt.data, b"dnssec_prover_test");
+                       assert_eq!(txt.data.as_vec(), b"dnssec_prover_test");
                } else { panic!(); }
                if let RR::CName(cname) = &verified_rrs.verified_rrs[2] {
                        assert_eq!(cname.name.as_str(), "cname_test.dnssec_proof_tests.bitcoin.ninja.");
@@ -1057,7 +1057,7 @@ mod tests {
                assert_eq!(filtered_rrs.len(), 1);
                if let RR::Txt(txt) = &filtered_rrs[0] {
                        assert_eq!(txt.name.as_str(), "txt_test.dnssec_proof_tests.bitcoin.ninja.");
-                       assert_eq!(txt.data, b"dnssec_prover_test");
+                       assert_eq!(txt.data.as_vec(), b"dnssec_prover_test");
                } else { panic!(); }
        }
 
@@ -1096,7 +1096,7 @@ mod tests {
                assert_eq!(verified_rrs.verified_rrs.len(), 2);
                if let RR::Txt(txt) = &verified_rrs.verified_rrs[0] {
                        assert_eq!(txt.name.as_str(), "asdf.wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
-                       assert_eq!(txt.data, b"wildcard_test");
+                       assert_eq!(txt.data.as_vec(), b"wildcard_test");
                } else { panic!(); }
                if let RR::CName(cname) = &verified_rrs.verified_rrs[1] {
                        assert_eq!(cname.name.as_str(), "asdf.cname_wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
@@ -1108,7 +1108,7 @@ mod tests {
                assert_eq!(filtered_rrs.len(), 1);
                if let RR::Txt(txt) = &filtered_rrs[0] {
                        assert_eq!(txt.name.as_str(), "asdf.wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
-                       assert_eq!(txt.data, b"wildcard_test");
+                       assert_eq!(txt.data.as_vec(), b"wildcard_test");
                } else { panic!(); }
        }
 
@@ -1130,7 +1130,7 @@ mod tests {
                assert_eq!(filtered_rrs.len(), 1);
                if let RR::Txt(txt) = &filtered_rrs[0] {
                        assert_eq!(txt.name.as_str(), "a.nsec_tests.dnssec_proof_tests.bitcoin.ninja.");
-                       assert_eq!(txt.data, b"txt_a");
+                       assert_eq!(txt.data.as_vec(), b"txt_a");
                } else { panic!(); }
        }
 
@@ -1161,7 +1161,7 @@ mod tests {
                        assert_eq!(filtered_rrs.len(), 1);
                        if let RR::Txt(txt) = &filtered_rrs[0] {
                                assert_eq!(txt.name, name);
-                               assert_eq!(txt.data, b"wildcard_test");
+                               assert_eq!(txt.data.as_vec(), b"wildcard_test");
                        } else { panic!(); }
                        Ok(())
                };