hickory_proto/rr/rdata/
txt.rs

1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! text records for storing arbitrary data
9use alloc::{boxed::Box, string::String, vec::Vec};
10use core::fmt;
11use core::slice::Iter;
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{
17    error::ProtoResult,
18    rr::{RData, RecordData, RecordDataDecodable, RecordType},
19    serialize::binary::*,
20};
21
22/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
23///
24/// ```text
25/// 3.3.14. TXT RDATA format
26///
27///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
28///     /                   TXT-DATA                    /
29///     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30///
31///
32/// TXT RRs are used to hold descriptive text.  The semantics of the text
33/// depends on the domain where it is found.
34/// ```
35#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
36#[derive(Debug, PartialEq, Eq, Hash, Clone)]
37pub struct TXT {
38    txt_data: Box<[Box<[u8]>]>,
39}
40
41impl TXT {
42    /// Creates a new TXT record data.
43    ///
44    /// # Arguments
45    ///
46    /// * `txt_data` - the set of strings which make up the txt_data.
47    ///
48    /// # Return value
49    ///
50    /// The new TXT record data.
51    pub fn new(txt_data: Vec<String>) -> Self {
52        Self {
53            txt_data: txt_data
54                .into_iter()
55                .map(|s| s.into_bytes().into_boxed_slice())
56                .collect::<Vec<_>>()
57                .into_boxed_slice(),
58        }
59    }
60
61    /// Creates a new TXT record data from bytes.
62    /// Allows creating binary record data.
63    ///
64    /// # Arguments
65    ///
66    /// * `txt_data` - the set of bytes which make up the txt_data.
67    ///
68    /// # Return value
69    ///
70    /// The new TXT record data.
71    pub fn from_bytes(txt_data: Vec<&[u8]>) -> Self {
72        Self {
73            txt_data: txt_data
74                .into_iter()
75                .map(|s| s.to_vec().into_boxed_slice())
76                .collect::<Vec<_>>()
77                .into_boxed_slice(),
78        }
79    }
80
81    /// ```text
82    /// TXT-DATA        One or more <character-string>s.
83    /// ```
84    pub fn txt_data(&self) -> &[Box<[u8]>] {
85        &self.txt_data
86    }
87
88    /// Returns an iterator over the arrays in the txt data
89    pub fn iter(&self) -> Iter<'_, Box<[u8]>> {
90        self.txt_data.iter()
91    }
92}
93
94impl BinEncodable for TXT {
95    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
96        for s in self.txt_data() {
97            encoder.emit_character_data(s)?;
98        }
99
100        Ok(())
101    }
102}
103
104impl RecordDataDecodable<'_> for TXT {
105    fn read_data(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<Self> {
106        let data_len = decoder.len();
107        let mut strings = Vec::with_capacity(1);
108
109        // no unsafe usage of rdata length after this point
110        let rdata_length =
111            rdata_length.map(|u| u as usize).unverified(/*used as a higher bound, safely*/);
112        while data_len - decoder.len() < rdata_length {
113            let string = decoder.read_character_data()?.unverified(/*any data should be validate in TXT usage*/);
114            strings.push(string.to_vec().into_boxed_slice());
115        }
116        Ok(Self {
117            txt_data: strings.into_boxed_slice(),
118        })
119    }
120}
121
122impl RecordData for TXT {
123    fn try_from_rdata(data: RData) -> Result<Self, RData> {
124        match data {
125            RData::TXT(data) => Ok(data),
126            _ => Err(data),
127        }
128    }
129
130    fn try_borrow(data: &RData) -> Option<&Self> {
131        match data {
132            RData::TXT(data) => Some(data),
133            _ => None,
134        }
135    }
136
137    fn record_type(&self) -> RecordType {
138        RecordType::TXT
139    }
140
141    fn into_rdata(self) -> RData {
142        RData::TXT(self)
143    }
144}
145
146impl fmt::Display for TXT {
147    /// Format a [TXT] with lossy conversion of invalid utf8.
148    ///
149    /// ## Case of invalid utf8
150    ///
151    /// Invalid utf8 will be converted to:
152    /// `U+FFFD REPLACEMENT CHARACTER`, which looks like this: �
153    ///
154    /// Same behaviour as `alloc::string::String::from_utf8_lossy`.
155    /// ```rust
156    /// # use hickory_proto::rr::rdata::TXT;
157    /// let first_bytes = b"Invalid utf8 <\xF0\x90\x80>.";
158    /// let second_bytes = b" Valid utf8 <\xF0\x9F\xA4\xA3>";
159    /// let rdata: Vec<&[u8]> = vec![first_bytes, second_bytes];
160    /// let txt = TXT::from_bytes(rdata);
161    ///
162    /// let tested = format!("{}", txt);
163    /// assert_eq!(
164    ///     tested.as_bytes(),
165    ///     b"Invalid utf8 <\xEF\xBF\xBD>. Valid utf8 <\xF0\x9F\xA4\xA3>",
166    ///     "Utf8 lossy conversion error! Mismatch between input and expected"
167    /// );
168    /// ```
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
170        for txt in self.txt_data.iter() {
171            f.write_str(&String::from_utf8_lossy(txt))?;
172        }
173
174        Ok(())
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    #![allow(clippy::dbg_macro, clippy::print_stdout)]
181
182    use alloc::string::ToString;
183    #[cfg(feature = "std")]
184    use std::println;
185
186    use super::*;
187
188    #[test]
189    fn test() {
190        let rdata = TXT::new(vec!["Test me some".to_string(), "more please".to_string()]);
191
192        let mut bytes = Vec::new();
193        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
194        assert!(rdata.emit(&mut encoder).is_ok());
195        let bytes = encoder.into_bytes();
196
197        #[cfg(feature = "std")]
198        println!("bytes: {bytes:?}");
199
200        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
201        let restrict = Restrict::new(bytes.len() as u16);
202        let read_rdata = TXT::read_data(&mut decoder, restrict).expect("Decoding error");
203        assert_eq!(rdata, read_rdata);
204    }
205
206    #[test]
207    fn publish_binary_txt_record() {
208        let bin_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
209        let rdata = TXT::from_bytes(vec![b"Test me some", &bin_data]);
210
211        let mut bytes = Vec::new();
212        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
213        assert!(rdata.emit(&mut encoder).is_ok());
214        let bytes = encoder.into_bytes();
215
216        #[cfg(feature = "std")]
217        println!("bytes: {bytes:?}");
218
219        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
220        let restrict = Restrict::new(bytes.len() as u16);
221        let read_rdata = TXT::read_data(&mut decoder, restrict).expect("Decoding error");
222        assert_eq!(rdata, read_rdata);
223    }
224}