1use core::fmt::{self, Write};
2
3use either::Either;
4
5use super::error::{ProtoError, not_enough_read_data};
6
7#[derive(Debug, Default, Clone, Copy)]
22pub struct Txt<'container, 'innards> {
23 repr: Repr<'container, 'innards>,
24}
25
26impl<'container, 'innards> From<&'container [&'innards str]> for Txt<'container, 'innards> {
27 fn from(strings: &'container [&'innards str]) -> Self {
28 Self::from_strings(strings)
29 }
30}
31
32#[derive(Debug, Clone, Copy)]
33enum Repr<'container, 'innards> {
34 BytesStrings {
35 original: &'innards [u8],
37
38 start: usize,
40
41 end: usize,
43 },
44 Strings(&'container [&'innards str]),
45}
46
47impl Default for Repr<'_, '_> {
48 fn default() -> Self {
49 Self::Strings(&[])
50 }
51}
52
53impl<'container, 'innards> Txt<'container, 'innards> {
54 #[inline]
56 pub const fn from_strings(strings: &'container [&'innards str]) -> Self {
57 Self {
58 repr: Repr::Strings(strings),
59 }
60 }
61
62 #[inline]
64 pub const fn from_bytes(src: &'innards [u8]) -> Self {
65 Self::from_bytes_in(src, 0, src.len())
66 }
67
68 #[inline]
70 pub(super) const fn from_bytes_in(original: &'innards [u8], start: usize, end: usize) -> Self {
71 Self {
72 repr: Repr::BytesStrings {
73 original,
74 start,
75 end,
76 },
77 }
78 }
79
80 #[inline]
82 pub const fn strings(&self) -> Strings<'container, 'innards> {
83 let repr = match &self.repr {
84 Repr::BytesStrings {
85 original,
86 start,
87 end,
88 } => StringsRepr::Bytes {
89 original,
90 position: *start,
91 end: *end,
92 },
93 Repr::Strings(strings) => StringsRepr::Strings {
94 strings,
95 position: 0,
96 },
97 };
98
99 Strings { repr }
100 }
101
102 #[inline]
104 pub fn repr(&self) -> Either<&'container [&'innards str], &'innards [u8]> {
105 match &self.repr {
106 Repr::BytesStrings {
107 original,
108 start,
109 end,
110 } => Either::Right(&original[*start..*end]),
111 Repr::Strings(strings) => Either::Left(strings),
112 }
113 }
114}
115
116#[derive(Clone, Copy, Debug)]
118pub struct Str<'a> {
119 repr: StrRepr<'a>,
120}
121
122impl fmt::Display for Str<'_> {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match &self.repr {
125 StrRepr::Bytes {
126 original,
127 start,
128 length,
129 } => {
130 let bytes = &original[*start..*start + *length];
131 for &byte in bytes {
133 match byte {
134 b'"' | b'\\' => {
135 f.write_str("\\")?;
136 f.write_char(byte as char)?;
137 }
138 b if (b' '..=b'~').contains(&b) => {
139 f.write_char(b as char)?;
140 }
141 b => {
142 f.write_str(
143 simdutf8::basic::from_utf8(escape_bytes(b).as_slice())
144 .expect("escape bytes must be valid utf8"),
145 )?;
146 }
147 }
148 }
149 Ok(())
150 }
151 StrRepr::String(s) => write!(f, "{}", s),
152 }
153 }
154}
155
156#[derive(Clone, Copy, Debug)]
158enum StrRepr<'a> {
159 Bytes {
161 original: &'a [u8],
163 start: usize,
165 length: usize,
167 },
168 String(&'a str),
170}
171
172impl<'a> Str<'a> {
173 fn from_bytes(original: &'a [u8], start: usize, length: usize) -> Self {
175 Self {
176 repr: StrRepr::Bytes {
177 original,
178 start,
179 length,
180 },
181 }
182 }
183
184 pub fn as_bytes(&self) -> &'a [u8] {
186 match self.repr {
187 StrRepr::Bytes {
188 original,
189 start,
190 length,
191 } => &original[start..start + length],
192 StrRepr::String(s) => s.as_bytes(),
193 }
194 }
195
196 #[inline]
198 pub const fn new(s: &'a str) -> Self {
199 Self {
200 repr: StrRepr::String(s),
201 }
202 }
203}
204
205enum StringsRepr<'container, 'innards> {
207 Bytes {
208 original: &'innards [u8],
209 position: usize,
210 end: usize,
211 },
212 Strings {
213 strings: &'container [&'innards str],
214 position: usize,
215 },
216}
217
218pub struct Strings<'container, 'innards> {
220 repr: StringsRepr<'container, 'innards>,
221}
222
223impl<'innards> Iterator for Strings<'_, 'innards> {
224 type Item = Result<Str<'innards>, ProtoError>;
225
226 fn next(&mut self) -> Option<Self::Item> {
227 match &mut self.repr {
228 StringsRepr::Bytes {
229 original,
230 position,
231 end,
232 } => {
233 if *position >= *end {
234 return None;
235 }
236
237 let result = decode_txt_segment(original, *position, *end);
238 match result {
239 Ok((segment, new_position)) => {
240 *position = new_position;
241 Some(Ok(segment))
242 }
243 Err(e) => {
244 *position = *end;
246 Some(Err(e))
247 }
248 }
249 }
250 StringsRepr::Strings { strings, position } => {
251 if *position >= strings.len() {
252 return None;
253 }
254
255 let string = strings[*position];
256 *position += 1;
257 Some(Ok(Str::new(string)))
258 }
259 }
260 }
261}
262
263fn decode_txt_segment(
265 msg: &[u8],
266 mut offset: usize,
267 end: usize,
268) -> Result<(Str<'_>, usize), ProtoError> {
269 if offset + 1 > msg.len() || offset >= end {
270 return Err(not_enough_read_data(1, 0));
271 }
272
273 let length = msg[offset] as usize;
274 offset += 1;
275 let content_start = offset;
276 let content_end = content_start + length;
277
278 if content_end > msg.len() {
279 return Err(not_enough_read_data(length, content_end - msg.len()));
280 }
281
282 if content_end > end {
283 return Err(not_enough_read_data(length, content_end - end));
284 }
285
286 let mut consumed = 0;
287 for (i, &b) in msg[offset..offset + length].iter().enumerate() {
288 match () {
289 () if (b == b'"' || b == b'\\') || !(b' '..=b'~').contains(&b) => {
290 consumed = i + 1;
291 }
292 _ => {}
293 }
294 }
295
296 if consumed == 0 {
297 return simdutf8::compat::from_utf8(&msg[offset..offset + length])
299 .map(|s| (Str::new(s), offset + length))
300 .map_err(Into::into);
301 }
302
303 let segment = Str::from_bytes(msg, content_start, length);
304 Ok((segment, content_end))
305}
306
307#[inline]
336const fn escape_bytes(b: u8) -> [u8; 4] {
337 let mut buf = [0; 4];
338 buf[0] = b'\\';
339 buf[1] = b'0' + (b / 100);
340 buf[2] = b'0' + ((b / 10) % 10);
341 buf[3] = b'0' + (b % 10);
342 buf
343}