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',
26 'F',
27];
28
29#[must_use]
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub struct CStringLiteralCodec;
41
42impl CStringLiteralCodec {
43 #[inline]
48 pub fn new() -> Self {
49 Self
50 }
51
52 #[inline]
60 #[must_use]
61 pub fn encode(&self, bytes: &[u8]) -> String {
62 let mut output = String::with_capacity(bytes.len());
63 for byte in bytes {
64 push_encoded_byte(*byte, &mut output);
65 }
66 output
67 }
68
69 #[inline]
84 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
85 let input = text.as_bytes();
86 let mut output = Vec::with_capacity(text.len());
87 let mut index = 0;
88 while index < input.len() {
89 let (decoded, consumed) = decode_c_string_literal_unit(
90 input,
91 index,
92 CStringLiteralParseContext::CompleteText(text),
93 )
94 .map_err(|error| {
95 c_string_parse_error_to_misc(error, input, index)
96 })?;
97 debug_assert!(consumed > 0);
98 output.push(decoded);
99 index += consumed;
100 }
101 Ok(output)
102 }
103}
104
105impl ValueEncoder<[u8]> for CStringLiteralCodec {
106 type Error = MiscCodecError;
107 type Output = String;
108
109 #[inline]
111 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
112 Ok(CStringLiteralCodec::encode(self, input))
113 }
114}
115
116impl ValueDecoder<str> for CStringLiteralCodec {
117 type Error = MiscCodecError;
118 type Output = Vec<u8>;
119
120 #[inline]
122 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
123 CStringLiteralCodec::decode(self, input)
124 }
125}
126
127impl Codec for CStringLiteralCodec {
128 type Value = u8;
129 type Unit = u8;
130 type DecodeError = MiscCodecError;
131 type EncodeError = MiscCodecError;
132
133 const MIN_UNITS_PER_VALUE: usize = 1;
134 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
135
136 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
137
138 #[inline(always)]
140 fn encode_len(&self, value: &u8) -> usize {
141 encoded_byte_len(*value)
142 }
143
144 #[inline]
150 unsafe fn decode(
151 &mut self,
152 input: &[u8],
153 input_index: usize,
154 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>>
155 {
156 debug_assert!(input_index < input.len());
157
158 let (value, consumed) =
159 decode_c_string_literal_byte(input, input_index).map_err(
160 |error| {
161 map_c_string_parse_error_to_decode_failure(
162 error,
163 input,
164 input_index,
165 )
166 },
167 )?;
168 debug_assert!(consumed > 0);
169 let consumed = nonzero(consumed);
172 Ok((value, consumed))
173 }
174
175 #[inline]
181 unsafe fn decode_eof(
182 &mut self,
183 input: &[u8],
184 input_index: usize,
185 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>>
186 {
187 debug_assert!(input_index < input.len());
188
189 let (value, consumed) = decode_c_string_literal_unit(
190 input,
191 input_index,
192 CStringLiteralParseContext::EofBytes,
193 )
194 .map_err(|error| {
195 map_c_string_parse_error_to_decode_failure(
196 error,
197 input,
198 input_index,
199 )
200 })?;
201 debug_assert!(consumed > 0);
202 let consumed = nonzero(consumed);
203 Ok((value, consumed))
204 }
205
206 #[inline]
212 unsafe fn encode(
213 &mut self,
214 value: &u8,
215 output: &mut [u8],
216 output_index: usize,
217 ) -> Result<usize, Self::EncodeError> {
218 let required = encoded_byte_len(*value);
219 debug_assert!(output_index + required <= output.len());
220
221 let written = write_encoded_byte(*value, output, output_index);
222 debug_assert_eq!(written, required);
223 Ok(required)
224 }
225}
226
227#[inline]
233fn push_encoded_byte(byte: u8, output: &mut String) {
234 match byte {
235 b'\'' => output.push_str("\\'"),
236 b'"' => output.push_str("\\\""),
237 b'?' => output.push_str("\\?"),
238 b'\\' => output.push_str("\\\\"),
239 0x07 => output.push_str("\\a"),
240 0x08 => output.push_str("\\b"),
241 0x0c => output.push_str("\\f"),
242 b'\n' => output.push_str("\\n"),
243 b'\r' => output.push_str("\\r"),
244 b'\t' => output.push_str("\\t"),
245 0x0b => output.push_str("\\v"),
246 b' '..=b'~' => output.push(byte as char),
247 _ => {
248 output.push('\\');
249 output.push('x');
250 output.push(uppercase_hex_digit(byte >> 4));
251 output.push(uppercase_hex_digit(byte & 0x0f));
252 }
253 }
254}
255
256#[inline]
268fn decode_c_string_literal_byte(
269 input: &[u8],
270 index: usize,
271) -> Result<(u8, usize), ParseError> {
272 decode_c_string_literal_unit(
273 input,
274 index,
275 CStringLiteralParseContext::StreamingBytes,
276 )
277}
278
279#[inline]
292fn decode_c_string_literal_unit(
293 input: &[u8],
294 index: usize,
295 context: CStringLiteralParseContext<'_>,
296) -> Result<(u8, usize), ParseError> {
297 debug_assert!(index < input.len());
298 let available = input.len() - index;
299 let byte = input[index];
300 if byte != b'\\' {
301 validate_source_unit(input, index, byte, context)?;
302 return Ok((byte, 1));
303 }
304 if available < 2 {
305 return Err(context.trailing_escape_error(index, available));
306 }
307 let escape = input[index + 1];
308 match escape {
309 b' ' => Ok((b' ', 2)),
310 b'\'' => Ok((b'\'', 2)),
311 b'"' => Ok((b'"', 2)),
312 b'?' => Ok((b'?', 2)),
313 b'\\' => Ok((b'\\', 2)),
314 b'a' => Ok((0x07, 2)),
315 b'b' => Ok((0x08, 2)),
316 b'f' => Ok((0x0c, 2)),
317 b'n' => Ok((b'\n', 2)),
318 b'r' => Ok((b'\r', 2)),
319 b't' => Ok((b'\t', 2)),
320 b'v' => Ok((0x0b, 2)),
321 b'x' | b'X' => {
322 if !context.is_complete_text() {
323 ensure_variable_hex_escape_complete(input, index, available)?;
324 }
325 parse_variable_hex_escape_units(input, index)
326 }
327 b'u' => {
328 if matches!(context, CStringLiteralParseContext::CompleteText(_))
329 || context.is_streaming()
330 {
331 ensure_fixed_escape_complete(available, nonzero(6))?;
332 }
333 parse_fixed_hex_escape_units(input, index, 4, context)
334 }
335 b'U' => {
336 if matches!(context, CStringLiteralParseContext::CompleteText(_))
337 || context.is_streaming()
338 {
339 ensure_fixed_escape_complete(available, nonzero(10))?;
340 }
341 parse_fixed_hex_escape_units(input, index, 8, context)
342 }
343 b'0'..=b'7' => {
344 if context.is_streaming() {
345 ensure_octal_escape_complete(input, index, available)?;
346 }
347 Ok(parse_octal_escape_units(input, index))
348 }
349 _ => Err(invalid_escape(
350 index,
351 &context.escape_fragment(input, index, index + 2),
352 "unsupported escape sequence",
353 )
354 .into()),
355 }
356}
357
358#[inline]
368fn ensure_variable_hex_escape_complete(
369 input: &[u8],
370 index: usize,
371 available: usize,
372) -> Result<(), ParseError> {
373 if available < 3 {
374 return Err(ParseError::Incomplete {
375 required: nonzero(3),
376 });
377 }
378 let mut digit_count = 0usize;
379 while digit_count < 2 {
380 let Some(&byte) = input.get(index + 2 + digit_count) else {
381 break;
382 };
383 if hex_value(char::from(byte)).is_none() {
384 break;
385 }
386 digit_count += 1;
387 }
388 if digit_count == 1 && index + 3 == input.len() {
389 return Err(ParseError::Incomplete {
390 required: nonzero(4),
391 });
392 }
393 Ok(())
394}
395
396#[inline]
405fn ensure_fixed_escape_complete(
406 available: usize,
407 required: core::num::NonZeroUsize,
408) -> Result<(), ParseError> {
409 if available < required.get() {
410 return Err(ParseError::Incomplete { required });
411 }
412 Ok(())
413}
414
415#[inline]
425fn ensure_octal_escape_complete(
426 input: &[u8],
427 index: usize,
428 _available: usize,
429) -> Result<(), ParseError> {
430 let mut digit_count = 0usize;
431 while digit_count < 3 {
432 let Some(&byte) = input.get(index + 1 + digit_count) else {
433 break;
434 };
435 if octal_value(char::from(byte)).is_none() {
436 break;
437 }
438 digit_count += 1;
439 }
440 if digit_count < 3 && index + 1 + digit_count == input.len() {
441 return Err(ParseError::Incomplete {
442 required: nonzero(2 + digit_count),
443 });
444 }
445 Ok(())
446}
447
448#[inline]
460fn validate_source_unit(
461 input: &[u8],
462 index: usize,
463 byte: u8,
464 context: CStringLiteralParseContext<'_>,
465) -> Result<(), ParseError> {
466 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
467 return Ok(());
468 }
469 Err(MiscCodecError::InvalidCharacter {
470 index,
471 character: context.source_character(input, index),
472 reason: context.raw_source_reason().to_owned(),
473 }
474 .into())
475}
476
477#[inline]
490fn parse_variable_hex_escape_units(
491 input: &[u8],
492 marker_index: usize,
493) -> Result<(u8, usize), ParseError> {
494 let mut value = 0u8;
495 let mut digit_count = 0usize;
496 let mut index = marker_index + 2;
497 while digit_count < 2 {
498 let Some(&byte) = input.get(index) else {
499 break;
500 };
501 let Some(digit) = hex_value(char::from(byte)) else {
502 break;
503 };
504 value = (value << 4) | digit;
505 index += 1;
506 digit_count += 1;
507 }
508 if digit_count == 0 {
509 return Err(invalid_escape(
510 marker_index,
511 "\\x",
512 "expected at least one hexadecimal digit",
513 )
514 .into());
515 }
516 Ok((value, 2 + digit_count))
517}
518
519#[inline]
534fn parse_fixed_hex_escape_units(
535 input: &[u8],
536 marker_index: usize,
537 digits: usize,
538 context: CStringLiteralParseContext<'_>,
539) -> Result<(u8, usize), ParseError> {
540 let mut value = 0u32;
541 let mut index = marker_index + 2;
542 for _ in 0..digits {
543 let Some(_) = input.get(index) else {
544 return Err(invalid_escape(
545 marker_index,
546 &context.escape_fragment(input, marker_index, input.len()),
547 "incomplete universal character escape",
548 )
549 .into());
550 };
551 let character = context.source_character(input, index);
552 let Some(digit) = hex_value(character) else {
553 return Err(MiscCodecError::InvalidDigit {
554 radix: 16,
555 index,
556 character,
557 }
558 .into());
559 };
560 value = (value << 4) | u32::from(digit);
561 index += 1;
562 }
563 if value > u32::from(u8::MAX) {
564 return Err(invalid_escape(
565 marker_index,
566 &context.escape_fragment(input, marker_index, index),
567 "universal character value must fit in one byte",
568 )
569 .into());
570 }
571 Ok((value as u8, 2 + digits))
572}
573
574#[inline]
584fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
585 let mut value = 0u16;
586 let mut digit_count = 0usize;
587 let mut index = marker_index + 1;
588 while digit_count < 3 {
589 let Some(&byte) = input.get(index) else {
590 break;
591 };
592 let Some(digit) = octal_value(char::from(byte)) else {
593 break;
594 };
595 value = (value << 3) | u16::from(digit);
596 index += 1;
597 digit_count += 1;
598 }
599 (value as u8, 1 + digit_count)
600}
601
602#[must_use]
610#[inline(always)]
611fn encoded_byte_len(byte: u8) -> usize {
612 match byte {
613 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r'
614 | b'\t' | 0x0b => 2,
615 b' '..=b'~' => 1,
616 _ => 4,
617 }
618}
619
620#[inline]
630fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
631 match byte {
632 b'\'' => write_ascii_escape(output, index, b'\''),
633 b'"' => write_ascii_escape(output, index, b'"'),
634 b'?' => write_ascii_escape(output, index, b'?'),
635 b'\\' => write_ascii_escape(output, index, b'\\'),
636 0x07 => write_ascii_escape(output, index, b'a'),
637 0x08 => write_ascii_escape(output, index, b'b'),
638 0x0c => write_ascii_escape(output, index, b'f'),
639 b'\n' => write_ascii_escape(output, index, b'n'),
640 b'\r' => write_ascii_escape(output, index, b'r'),
641 b'\t' => write_ascii_escape(output, index, b't'),
642 0x0b => write_ascii_escape(output, index, b'v'),
643 b' '..=b'~' => {
644 output[index] = byte;
645 1
646 }
647 _ => {
648 output[index] = b'\\';
649 output[index + 1] = b'x';
650 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
651 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
652 4
653 }
654 }
655}
656
657#[inline(always)]
667fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
668 output[index] = b'\\';
669 output[index + 1] = escape;
670 2
671}
672
673fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
683 let bounded_end = end.min(input.len());
684 input[start..bounded_end]
685 .iter()
686 .map(|byte| char::from(*byte))
687 .collect()
688}
689
690#[inline(always)]
698fn hex_value(character: char) -> Option<u8> {
699 match character {
700 '0'..='9' => Some(character as u8 - b'0'),
701 'a'..='f' => Some(character as u8 - b'a' + 10),
702 'A'..='F' => Some(character as u8 - b'A' + 10),
703 _ => None,
704 }
705}
706
707#[inline(always)]
715fn octal_value(character: char) -> Option<u8> {
716 match character {
717 '0'..='7' => Some(character as u8 - b'0'),
718 _ => None,
719 }
720}
721
722#[inline(always)]
730fn uppercase_hex_digit(value: u8) -> char {
731 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
732}
733
734#[inline]
744fn c_string_parse_error_to_misc(
745 error: ParseError,
746 input: &[u8],
747 index: usize,
748) -> MiscCodecError {
749 match error {
750 ParseError::Incomplete { required, .. } => {
751 MiscCodecError::InvalidEscape {
752 index,
753 escape: escape_fragment(input, index, input.len()),
754 reason: format!(
755 "incomplete escape sequence; expected at least {required} units"
756 ),
757 }
758 }
759 ParseError::Invalid(error) => error,
760 }
761}
762
763#[inline]
765fn map_c_string_parse_error_to_decode_failure(
766 error: ParseError,
767 input: &[u8],
768 index: usize,
769) -> DecodeFailure<MiscCodecError> {
770 match error {
771 ParseError::Incomplete { required, .. } => {
772 DecodeFailure::incomplete(required)
773 }
774 ParseError::Invalid(error) => map_misc_decode_failure_with_consumed(
775 error,
776 c_string_invalid_consumed(input, index),
777 ),
778 }
779}
780
781#[inline]
783fn c_string_invalid_consumed(input: &[u8], index: usize) -> NonZeroUsize {
784 let available = input.len().saturating_sub(index);
785 let width = match input.get(index..index.saturating_add(2)) {
786 Some([b'\\', b'u']) => 6,
787 Some([b'\\', b'U']) => 10,
788 Some([b'\\', _]) => 2,
789 _ => 1,
790 };
791 NonZeroUsize::new(width.min(available).max(1))
792 .expect("malformed C string input has at least one unit")
793}
794
795pub(crate) fn invalid_escape(
805 index: usize,
806 escape: &str,
807 reason: &str,
808) -> MiscCodecError {
809 MiscCodecError::InvalidEscape {
810 index,
811 escape: escape.to_owned(),
812 reason: reason.to_owned(),
813 }
814}