1use core::num::NonZeroUsize;
11
12use qubit_codec::Codec;
13use qubit_codec::DecodeFailure;
14use qubit_codec::ValueDecoder;
15use qubit_codec::ValueEncoder;
16use qubit_utils::nonzero;
17
18use crate::MiscCodecError;
19use crate::MiscCodecResult;
20use crate::internal::CStringLiteralParseContext;
21use crate::internal::ParseError;
22use crate::misc_codec_error::map_misc_decode_failure_with_consumed;
23
24const UPPER_HEX_DIGITS: [char; 16] = [
25 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
26];
27
28#[must_use]
38#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
39pub struct CStringLiteralCodec;
40
41impl CStringLiteralCodec {
42 #[inline]
47 pub fn new() -> Self {
48 Self
49 }
50
51 #[inline]
59 #[must_use]
60 pub fn encode(&self, bytes: &[u8]) -> String {
61 let mut output = String::with_capacity(bytes.len());
62 for byte in bytes {
63 push_encoded_byte(*byte, &mut output);
64 }
65 output
66 }
67
68 #[inline]
83 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
84 let input = text.as_bytes();
85 let mut output = Vec::with_capacity(text.len());
86 let mut index = 0;
87 while index < input.len() {
88 let (decoded, consumed) = decode_c_string_literal_unit(
89 input,
90 index,
91 CStringLiteralParseContext::CompleteText(text),
92 )
93 .map_err(|error| c_string_parse_error_to_misc(error, input, index))?;
94 debug_assert!(consumed > 0);
95 output.push(decoded);
96 index += consumed;
97 }
98 Ok(output)
99 }
100}
101
102impl ValueEncoder<[u8]> for CStringLiteralCodec {
103 type Error = MiscCodecError;
104 type Output = String;
105
106 #[inline]
108 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
109 Ok(CStringLiteralCodec::encode(self, input))
110 }
111}
112
113impl ValueDecoder<str> for CStringLiteralCodec {
114 type Error = MiscCodecError;
115 type Output = Vec<u8>;
116
117 #[inline]
119 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
120 CStringLiteralCodec::decode(self, input)
121 }
122}
123
124impl Codec for CStringLiteralCodec {
125 type Value = u8;
126 type Unit = u8;
127 type DecodeError = MiscCodecError;
128 type EncodeError = MiscCodecError;
129
130 const MIN_UNITS_PER_VALUE: usize = 1;
131 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
132
133 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
134
135 #[inline(always)]
137 fn encode_len(&self, value: &u8) -> usize {
138 encoded_byte_len(*value)
139 }
140
141 #[inline]
147 unsafe fn decode(
148 &mut self,
149 input: &[u8],
150 input_index: usize,
151 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
152 debug_assert!(input_index < input.len());
153
154 let (value, consumed) =
155 decode_c_string_literal_byte(input, input_index).map_err(|error| {
156 map_c_string_parse_error_to_decode_failure(error, input, input_index)
157 })?;
158 debug_assert!(consumed > 0);
159 let consumed = nonzero(consumed);
162 Ok((value, consumed))
163 }
164
165 #[inline]
171 unsafe fn decode_eof(
172 &mut self,
173 input: &[u8],
174 input_index: usize,
175 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
176 debug_assert!(input_index < input.len());
177
178 let (value, consumed) =
179 decode_c_string_literal_unit(input, input_index, CStringLiteralParseContext::EofBytes)
180 .map_err(|error| {
181 map_c_string_parse_error_to_decode_failure(error, input, input_index)
182 })?;
183 debug_assert!(consumed > 0);
184 let consumed = nonzero(consumed);
185 Ok((value, consumed))
186 }
187
188 #[inline]
194 unsafe fn encode(
195 &mut self,
196 value: &u8,
197 output: &mut [u8],
198 output_index: usize,
199 ) -> Result<usize, Self::EncodeError> {
200 let required = encoded_byte_len(*value);
201 debug_assert!(output_index + required <= output.len());
202
203 let written = write_encoded_byte(*value, output, output_index);
204 debug_assert_eq!(written, required);
205 Ok(required)
206 }
207}
208
209#[inline]
215fn push_encoded_byte(byte: u8, output: &mut String) {
216 match byte {
217 b'\'' => output.push_str("\\'"),
218 b'"' => output.push_str("\\\""),
219 b'?' => output.push_str("\\?"),
220 b'\\' => output.push_str("\\\\"),
221 0x07 => output.push_str("\\a"),
222 0x08 => output.push_str("\\b"),
223 0x0c => output.push_str("\\f"),
224 b'\n' => output.push_str("\\n"),
225 b'\r' => output.push_str("\\r"),
226 b'\t' => output.push_str("\\t"),
227 0x0b => output.push_str("\\v"),
228 b' '..=b'~' => output.push(byte as char),
229 _ => {
230 output.push('\\');
231 output.push('x');
232 output.push(uppercase_hex_digit(byte >> 4));
233 output.push(uppercase_hex_digit(byte & 0x0f));
234 }
235 }
236}
237
238#[inline]
250fn decode_c_string_literal_byte(input: &[u8], index: usize) -> Result<(u8, usize), ParseError> {
251 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::StreamingBytes)
252}
253
254#[inline]
267fn decode_c_string_literal_unit(
268 input: &[u8],
269 index: usize,
270 context: CStringLiteralParseContext<'_>,
271) -> Result<(u8, usize), ParseError> {
272 debug_assert!(index < input.len());
273 let available = input.len() - index;
274 let byte = input[index];
275 if byte != b'\\' {
276 validate_source_unit(input, index, byte, context)?;
277 return Ok((byte, 1));
278 }
279 if available < 2 {
280 return Err(context.trailing_escape_error(index, available));
281 }
282 let escape = input[index + 1];
283 match escape {
284 b' ' => Ok((b' ', 2)),
285 b'\'' => Ok((b'\'', 2)),
286 b'"' => Ok((b'"', 2)),
287 b'?' => Ok((b'?', 2)),
288 b'\\' => Ok((b'\\', 2)),
289 b'a' => Ok((0x07, 2)),
290 b'b' => Ok((0x08, 2)),
291 b'f' => Ok((0x0c, 2)),
292 b'n' => Ok((b'\n', 2)),
293 b'r' => Ok((b'\r', 2)),
294 b't' => Ok((b'\t', 2)),
295 b'v' => Ok((0x0b, 2)),
296 b'x' | b'X' => {
297 if !context.is_complete_text() {
298 ensure_variable_hex_escape_complete(input, index, available)?;
299 }
300 parse_variable_hex_escape_units(input, index)
301 }
302 b'u' => {
303 if matches!(context, CStringLiteralParseContext::CompleteText(_))
304 || context.is_streaming()
305 {
306 ensure_fixed_escape_complete(available, nonzero(6))?;
307 }
308 parse_fixed_hex_escape_units(input, index, 4, context)
309 }
310 b'U' => {
311 if matches!(context, CStringLiteralParseContext::CompleteText(_))
312 || context.is_streaming()
313 {
314 ensure_fixed_escape_complete(available, nonzero(10))?;
315 }
316 parse_fixed_hex_escape_units(input, index, 8, context)
317 }
318 b'0'..=b'7' => {
319 if context.is_streaming() {
320 ensure_octal_escape_complete(input, index, available)?;
321 }
322 Ok(parse_octal_escape_units(input, index))
323 }
324 _ => Err(invalid_escape(
325 index,
326 &context.escape_fragment(input, index, index + 2),
327 "unsupported escape sequence",
328 )
329 .into()),
330 }
331}
332
333#[inline]
343fn ensure_variable_hex_escape_complete(
344 input: &[u8],
345 index: usize,
346 available: usize,
347) -> Result<(), ParseError> {
348 if available < 3 {
349 return Err(ParseError::Incomplete {
350 required: nonzero(3),
351 });
352 }
353 let mut digit_count = 0usize;
354 while digit_count < 2 {
355 let Some(&byte) = input.get(index + 2 + digit_count) else {
356 break;
357 };
358 if hex_value(char::from(byte)).is_none() {
359 break;
360 }
361 digit_count += 1;
362 }
363 if digit_count == 1 && index + 3 == input.len() {
364 return Err(ParseError::Incomplete {
365 required: nonzero(4),
366 });
367 }
368 Ok(())
369}
370
371#[inline]
380fn ensure_fixed_escape_complete(
381 available: usize,
382 required: core::num::NonZeroUsize,
383) -> Result<(), ParseError> {
384 if available < required.get() {
385 return Err(ParseError::Incomplete { required });
386 }
387 Ok(())
388}
389
390#[inline]
400fn ensure_octal_escape_complete(
401 input: &[u8],
402 index: usize,
403 _available: usize,
404) -> Result<(), ParseError> {
405 let mut digit_count = 0usize;
406 while digit_count < 3 {
407 let Some(&byte) = input.get(index + 1 + digit_count) else {
408 break;
409 };
410 if octal_value(char::from(byte)).is_none() {
411 break;
412 }
413 digit_count += 1;
414 }
415 if digit_count < 3 && index + 1 + digit_count == input.len() {
416 return Err(ParseError::Incomplete {
417 required: nonzero(2 + digit_count),
418 });
419 }
420 Ok(())
421}
422
423#[inline]
435fn validate_source_unit(
436 input: &[u8],
437 index: usize,
438 byte: u8,
439 context: CStringLiteralParseContext<'_>,
440) -> Result<(), ParseError> {
441 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
442 return Ok(());
443 }
444 Err(MiscCodecError::InvalidCharacter {
445 index,
446 character: context.source_character(input, index),
447 reason: context.raw_source_reason().to_owned(),
448 }
449 .into())
450}
451
452#[inline]
465fn parse_variable_hex_escape_units(
466 input: &[u8],
467 marker_index: usize,
468) -> Result<(u8, usize), ParseError> {
469 let mut value = 0u8;
470 let mut digit_count = 0usize;
471 let mut index = marker_index + 2;
472 while digit_count < 2 {
473 let Some(&byte) = input.get(index) else {
474 break;
475 };
476 let Some(digit) = hex_value(char::from(byte)) else {
477 break;
478 };
479 value = (value << 4) | digit;
480 index += 1;
481 digit_count += 1;
482 }
483 if digit_count == 0 {
484 return Err(invalid_escape(
485 marker_index,
486 "\\x",
487 "expected at least one hexadecimal digit",
488 )
489 .into());
490 }
491 Ok((value, 2 + digit_count))
492}
493
494#[inline]
509fn parse_fixed_hex_escape_units(
510 input: &[u8],
511 marker_index: usize,
512 digits: usize,
513 context: CStringLiteralParseContext<'_>,
514) -> Result<(u8, usize), ParseError> {
515 let mut value = 0u32;
516 let mut index = marker_index + 2;
517 for _ in 0..digits {
518 let Some(_) = input.get(index) else {
519 return Err(invalid_escape(
520 marker_index,
521 &context.escape_fragment(input, marker_index, input.len()),
522 "incomplete universal character escape",
523 )
524 .into());
525 };
526 let character = context.source_character(input, index);
527 let Some(digit) = hex_value(character) else {
528 return Err(MiscCodecError::InvalidDigit {
529 radix: 16,
530 index,
531 character,
532 }
533 .into());
534 };
535 value = (value << 4) | u32::from(digit);
536 index += 1;
537 }
538 if value > u32::from(u8::MAX) {
539 return Err(invalid_escape(
540 marker_index,
541 &context.escape_fragment(input, marker_index, index),
542 "universal character value must fit in one byte",
543 )
544 .into());
545 }
546 Ok((value as u8, 2 + digits))
547}
548
549#[inline]
559fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
560 let mut value = 0u16;
561 let mut digit_count = 0usize;
562 let mut index = marker_index + 1;
563 while digit_count < 3 {
564 let Some(&byte) = input.get(index) else {
565 break;
566 };
567 let Some(digit) = octal_value(char::from(byte)) else {
568 break;
569 };
570 value = (value << 3) | u16::from(digit);
571 index += 1;
572 digit_count += 1;
573 }
574 (value as u8, 1 + digit_count)
575}
576
577#[must_use]
585#[inline(always)]
586fn encoded_byte_len(byte: u8) -> usize {
587 match byte {
588 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r' | b'\t' | 0x0b => 2,
589 b' '..=b'~' => 1,
590 _ => 4,
591 }
592}
593
594#[inline]
604fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
605 match byte {
606 b'\'' => write_ascii_escape(output, index, b'\''),
607 b'"' => write_ascii_escape(output, index, b'"'),
608 b'?' => write_ascii_escape(output, index, b'?'),
609 b'\\' => write_ascii_escape(output, index, b'\\'),
610 0x07 => write_ascii_escape(output, index, b'a'),
611 0x08 => write_ascii_escape(output, index, b'b'),
612 0x0c => write_ascii_escape(output, index, b'f'),
613 b'\n' => write_ascii_escape(output, index, b'n'),
614 b'\r' => write_ascii_escape(output, index, b'r'),
615 b'\t' => write_ascii_escape(output, index, b't'),
616 0x0b => write_ascii_escape(output, index, b'v'),
617 b' '..=b'~' => {
618 output[index] = byte;
619 1
620 }
621 _ => {
622 output[index] = b'\\';
623 output[index + 1] = b'x';
624 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
625 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
626 4
627 }
628 }
629}
630
631#[inline(always)]
641fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
642 output[index] = b'\\';
643 output[index + 1] = escape;
644 2
645}
646
647fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
657 let bounded_end = end.min(input.len());
658 input[start..bounded_end]
659 .iter()
660 .map(|byte| char::from(*byte))
661 .collect()
662}
663
664#[inline(always)]
672fn hex_value(character: char) -> Option<u8> {
673 match character {
674 '0'..='9' => Some(character as u8 - b'0'),
675 'a'..='f' => Some(character as u8 - b'a' + 10),
676 'A'..='F' => Some(character as u8 - b'A' + 10),
677 _ => None,
678 }
679}
680
681#[inline(always)]
689fn octal_value(character: char) -> Option<u8> {
690 match character {
691 '0'..='7' => Some(character as u8 - b'0'),
692 _ => None,
693 }
694}
695
696#[inline(always)]
704fn uppercase_hex_digit(value: u8) -> char {
705 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
706}
707
708#[inline]
718fn c_string_parse_error_to_misc(error: ParseError, input: &[u8], index: usize) -> MiscCodecError {
719 match error {
720 ParseError::Incomplete { required, .. } => MiscCodecError::InvalidEscape {
721 index,
722 escape: escape_fragment(input, index, input.len()),
723 reason: format!("incomplete escape sequence; expected at least {required} units"),
724 },
725 ParseError::Invalid(error) => error,
726 }
727}
728
729#[inline]
731fn map_c_string_parse_error_to_decode_failure(
732 error: ParseError,
733 input: &[u8],
734 index: usize,
735) -> DecodeFailure<MiscCodecError> {
736 match error {
737 ParseError::Incomplete { required, .. } => DecodeFailure::incomplete(required),
738 ParseError::Invalid(error) => {
739 map_misc_decode_failure_with_consumed(error, c_string_invalid_consumed(input, index))
740 }
741 }
742}
743
744#[inline]
746fn c_string_invalid_consumed(input: &[u8], index: usize) -> NonZeroUsize {
747 let available = input.len().saturating_sub(index);
748 let width = match input.get(index..index.saturating_add(2)) {
749 Some([b'\\', b'u']) => 6,
750 Some([b'\\', b'U']) => 10,
751 Some([b'\\', _]) => 2,
752 _ => 1,
753 };
754 NonZeroUsize::new(width.min(available).max(1))
755 .expect("malformed C string input has at least one unit")
756}
757
758pub(crate) fn invalid_escape(index: usize, escape: &str, reason: &str) -> MiscCodecError {
768 MiscCodecError::InvalidEscape {
769 index,
770 escape: escape.to_owned(),
771 reason: reason.to_owned(),
772 }
773}