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]
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
70pub struct CStringLiteralCodec;
71
72impl CStringLiteralCodec {
73 #[inline]
78 pub fn new() -> Self {
79 Self
80 }
81
82 #[inline]
90 #[must_use]
91 pub fn encode(&self, bytes: &[u8]) -> String {
92 let mut output = String::with_capacity(bytes.len());
93 for byte in bytes {
94 push_encoded_byte(*byte, &mut output);
95 }
96 output
97 }
98
99 #[inline]
114 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
115 let input = text.as_bytes();
116 let mut output = Vec::with_capacity(text.len());
117 let mut index = 0;
118 while index < input.len() {
119 let (decoded, consumed) =
120 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::CompleteText(text))
121 .map_err(|error| c_string_parse_error_to_misc(error, input, index))?;
122 debug_assert!(consumed > 0);
123 output.push(decoded);
124 index += consumed;
125 }
126 Ok(output)
127 }
128}
129
130impl ValueEncoder<[u8]> for CStringLiteralCodec {
131 type Error = MiscCodecError;
132 type Output = String;
133
134 #[inline]
136 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
137 Ok(CStringLiteralCodec::encode(self, input))
138 }
139}
140
141impl ValueDecoder<str> for CStringLiteralCodec {
142 type Error = MiscCodecError;
143 type Output = Vec<u8>;
144
145 #[inline]
147 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
148 CStringLiteralCodec::decode(self, input)
149 }
150}
151
152impl Codec for CStringLiteralCodec {
153 type Value = u8;
154 type Unit = u8;
155 type DecodeError = MiscCodecError;
156 type EncodeError = MiscCodecError;
157
158 const MIN_UNITS_PER_VALUE: usize = 1;
159 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
160
161 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
162
163 #[inline]
165 fn encode_len(&self, value: &u8) -> usize {
166 encoded_byte_len(*value)
167 }
168
169 #[inline]
175 unsafe fn decode(
176 &mut self,
177 input: &[u8],
178 input_index: usize,
179 ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
180 debug_assert!(input_index < input.len());
181
182 let (value, consumed) = decode_c_string_literal_byte(input, input_index)
183 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
184 debug_assert!(consumed > 0);
185 let consumed = nonzero(consumed);
188 Ok((value, consumed))
189 }
190
191 #[inline]
197 unsafe fn decode_eof(
198 &mut self,
199 input: &[u8],
200 input_index: usize,
201 ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
202 debug_assert!(input_index < input.len());
203
204 let (value, consumed) = decode_c_string_literal_unit(input, input_index, CStringLiteralParseContext::EofBytes)
205 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
206 debug_assert!(consumed > 0);
207 let consumed = nonzero(consumed);
208 Ok((value, consumed))
209 }
210
211 #[inline]
217 unsafe fn encode(
218 &mut self,
219 value: &u8,
220 output: &mut [u8],
221 output_index: usize,
222 ) -> Result<usize, Self::EncodeError> {
223 let required = encoded_byte_len(*value);
224 debug_assert!(output_index + required <= output.len());
225
226 let written = write_encoded_byte(*value, output, output_index);
227 debug_assert_eq!(written, required);
228 Ok(required)
229 }
230}
231
232#[inline]
238fn push_encoded_byte(byte: u8, output: &mut String) {
239 match byte {
240 b'\'' => output.push_str("\\'"),
241 b'"' => output.push_str("\\\""),
242 b'?' => output.push_str("\\?"),
243 b'\\' => output.push_str("\\\\"),
244 0x07 => output.push_str("\\a"),
245 0x08 => output.push_str("\\b"),
246 0x0c => output.push_str("\\f"),
247 b'\n' => output.push_str("\\n"),
248 b'\r' => output.push_str("\\r"),
249 b'\t' => output.push_str("\\t"),
250 0x0b => output.push_str("\\v"),
251 b' '..=b'~' => output.push(byte as char),
252 _ => {
253 output.push('\\');
254 output.push('x');
255 output.push(uppercase_hex_digit(byte >> 4));
256 output.push(uppercase_hex_digit(byte & 0x0f));
257 }
258 }
259}
260
261#[inline]
273fn decode_c_string_literal_byte(input: &[u8], index: usize) -> Result<(u8, usize), ParseError> {
274 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::StreamingBytes)
275}
276
277#[inline]
290fn decode_c_string_literal_unit(
291 input: &[u8],
292 index: usize,
293 context: CStringLiteralParseContext<'_>,
294) -> Result<(u8, usize), ParseError> {
295 debug_assert!(index < input.len());
296 let available = input.len() - index;
297 let byte = input[index];
298 if byte != b'\\' {
299 validate_source_unit(input, index, byte, context)?;
300 return Ok((byte, 1));
301 }
302 if available < 2 {
303 return Err(context.trailing_escape_error(index, available));
304 }
305 let escape = input[index + 1];
306 match escape {
307 b' ' => Ok((b' ', 2)),
308 b'\'' => Ok((b'\'', 2)),
309 b'"' => Ok((b'"', 2)),
310 b'?' => Ok((b'?', 2)),
311 b'\\' => Ok((b'\\', 2)),
312 b'a' => Ok((0x07, 2)),
313 b'b' => Ok((0x08, 2)),
314 b'f' => Ok((0x0c, 2)),
315 b'n' => Ok((b'\n', 2)),
316 b'r' => Ok((b'\r', 2)),
317 b't' => Ok((b'\t', 2)),
318 b'v' => Ok((0x0b, 2)),
319 b'x' | b'X' => {
320 if !context.is_complete_text() {
321 ensure_variable_hex_escape_complete(input, index, available)?;
322 }
323 parse_variable_hex_escape_units(input, index)
324 }
325 b'u' => {
326 if context.is_streaming() && available < 6 {
327 validate_fixed_escape_prefix(input, index, 4)?;
328 }
329 if matches!(context, CStringLiteralParseContext::CompleteText(_)) || context.is_streaming() {
330 ensure_fixed_escape_complete(available, nonzero(6))?;
331 }
332 parse_fixed_hex_escape_units(input, index, 4, context)
333 }
334 b'U' => {
335 if context.is_streaming() && available < 10 {
336 validate_fixed_escape_prefix(input, index, 8)?;
337 }
338 if matches!(context, CStringLiteralParseContext::CompleteText(_)) || context.is_streaming() {
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(input: &[u8], index: usize, available: usize) -> Result<(), ParseError> {
369 if available < 3 {
370 return Err(ParseError::Incomplete { required: nonzero(3) });
371 }
372 let mut digit_count = 0usize;
373 while digit_count < 2 {
374 let Some(&byte) = input.get(index + 2 + digit_count) else {
375 break;
376 };
377 if hex_value(char::from(byte)).is_none() {
378 break;
379 }
380 digit_count += 1;
381 }
382 if digit_count == 1 && index + 3 == input.len() {
383 return Err(ParseError::Incomplete { required: nonzero(4) });
384 }
385 Ok(())
386}
387
388#[inline]
397fn ensure_fixed_escape_complete(available: usize, required: core::num::NonZeroUsize) -> Result<(), ParseError> {
398 if available < required.get() {
399 return Err(ParseError::Incomplete { required });
400 }
401 Ok(())
402}
403
404#[inline]
419fn validate_fixed_escape_prefix(input: &[u8], marker_index: usize, digits: usize) -> Result<(), ParseError> {
420 for (offset, byte) in input[marker_index + 2..].iter().copied().enumerate() {
421 let character = char::from(byte);
422 if hex_value(character).is_none() {
423 return Err(MiscCodecError::InvalidDigit {
424 radix: 16,
425 index: marker_index + 2 + offset,
426 character,
427 }
428 .into());
429 }
430 }
431 let visible_digits = &input[marker_index + 2..];
432 if visible_digits.iter().take(digits - 2).any(|byte| *byte != b'0') {
433 return Err(invalid_escape(
434 marker_index,
435 &escape_fragment(input, marker_index, input.len()),
436 "universal character value must fit in one byte",
437 )
438 .into());
439 }
440 Ok(())
441}
442
443#[inline]
453fn ensure_octal_escape_complete(input: &[u8], index: usize, _available: usize) -> Result<(), ParseError> {
454 let mut digit_count = 0usize;
455 while digit_count < 3 {
456 let Some(&byte) = input.get(index + 1 + digit_count) else {
457 break;
458 };
459 if octal_value(char::from(byte)).is_none() {
460 break;
461 }
462 digit_count += 1;
463 }
464 if digit_count < 3 && index + 1 + digit_count == input.len() {
465 return Err(ParseError::Incomplete {
466 required: nonzero(2 + digit_count),
467 });
468 }
469 Ok(())
470}
471
472#[inline]
484fn validate_source_unit(
485 input: &[u8],
486 index: usize,
487 byte: u8,
488 context: CStringLiteralParseContext<'_>,
489) -> Result<(), ParseError> {
490 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
491 return Ok(());
492 }
493 Err(MiscCodecError::InvalidCharacter {
494 index,
495 character: context.source_character(input, index),
496 reason: context.raw_source_reason().to_owned(),
497 }
498 .into())
499}
500
501#[inline]
514fn parse_variable_hex_escape_units(input: &[u8], marker_index: usize) -> Result<(u8, usize), ParseError> {
515 let mut value = 0u8;
516 let mut digit_count = 0usize;
517 let mut index = marker_index + 2;
518 while digit_count < 2 {
519 let Some(&byte) = input.get(index) else {
520 break;
521 };
522 let Some(digit) = hex_value(char::from(byte)) else {
523 break;
524 };
525 value = (value << 4) | digit;
526 index += 1;
527 digit_count += 1;
528 }
529 if digit_count == 0 {
530 return Err(invalid_escape(marker_index, "\\x", "expected at least one hexadecimal digit").into());
531 }
532 Ok((value, 2 + digit_count))
533}
534
535#[inline]
550fn parse_fixed_hex_escape_units(
551 input: &[u8],
552 marker_index: usize,
553 digits: usize,
554 context: CStringLiteralParseContext<'_>,
555) -> Result<(u8, usize), ParseError> {
556 let mut value = 0u32;
557 let mut index = marker_index + 2;
558 for _ in 0..digits {
559 let Some(_) = input.get(index) else {
560 return Err(invalid_escape(
561 marker_index,
562 &context.escape_fragment(input, marker_index, input.len()),
563 "incomplete universal character escape",
564 )
565 .into());
566 };
567 let character = context.source_character(input, index);
568 let Some(digit) = hex_value(character) else {
569 return Err(MiscCodecError::InvalidDigit {
570 radix: 16,
571 index,
572 character,
573 }
574 .into());
575 };
576 value = (value << 4) | u32::from(digit);
577 index += 1;
578 }
579 if value > u32::from(u8::MAX) {
580 return Err(invalid_escape(
581 marker_index,
582 &context.escape_fragment(input, marker_index, index),
583 "universal character value must fit in one byte",
584 )
585 .into());
586 }
587 Ok((value as u8, 2 + digits))
588}
589
590#[inline]
600fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
601 let mut value = 0u16;
602 let mut digit_count = 0usize;
603 let mut index = marker_index + 1;
604 while digit_count < 3 {
605 let Some(&byte) = input.get(index) else {
606 break;
607 };
608 let Some(digit) = octal_value(char::from(byte)) else {
609 break;
610 };
611 value = (value << 3) | u16::from(digit);
612 index += 1;
613 digit_count += 1;
614 }
615 (value as u8, 1 + digit_count)
616}
617
618#[must_use]
626#[inline]
627fn encoded_byte_len(byte: u8) -> usize {
628 match byte {
629 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r' | b'\t' | 0x0b => 2,
630 b' '..=b'~' => 1,
631 _ => 4,
632 }
633}
634
635#[inline]
645fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
646 match byte {
647 b'\'' => write_ascii_escape(output, index, b'\''),
648 b'"' => write_ascii_escape(output, index, b'"'),
649 b'?' => write_ascii_escape(output, index, b'?'),
650 b'\\' => write_ascii_escape(output, index, b'\\'),
651 0x07 => write_ascii_escape(output, index, b'a'),
652 0x08 => write_ascii_escape(output, index, b'b'),
653 0x0c => write_ascii_escape(output, index, b'f'),
654 b'\n' => write_ascii_escape(output, index, b'n'),
655 b'\r' => write_ascii_escape(output, index, b'r'),
656 b'\t' => write_ascii_escape(output, index, b't'),
657 0x0b => write_ascii_escape(output, index, b'v'),
658 b' '..=b'~' => {
659 output[index] = byte;
660 1
661 }
662 _ => {
663 output[index] = b'\\';
664 output[index + 1] = b'x';
665 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
666 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
667 4
668 }
669 }
670}
671
672#[inline]
682fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
683 output[index] = b'\\';
684 output[index + 1] = escape;
685 2
686}
687
688fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
701 let bounded_end = end.min(input.len());
702 input[start..bounded_end].iter().map(|byte| char::from(*byte)).collect()
703}
704
705#[inline]
713fn hex_value(character: char) -> Option<u8> {
714 match character {
715 '0'..='9' => Some(character as u8 - b'0'),
716 'a'..='f' => Some(character as u8 - b'a' + 10),
717 'A'..='F' => Some(character as u8 - b'A' + 10),
718 _ => None,
719 }
720}
721
722#[inline]
730fn octal_value(character: char) -> Option<u8> {
731 match character {
732 '0'..='7' => Some(character as u8 - b'0'),
733 _ => None,
734 }
735}
736
737#[inline]
745fn uppercase_hex_digit(value: u8) -> char {
746 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
747}
748
749#[inline]
759fn c_string_parse_error_to_misc(error: ParseError, input: &[u8], index: usize) -> MiscCodecError {
760 match error {
761 ParseError::Incomplete { required, .. } => MiscCodecError::InvalidEscape {
762 index,
763 escape: escape_fragment(input, index, input.len()),
764 reason: format!("incomplete escape sequence; expected at least {required} units"),
765 },
766 ParseError::Invalid(error) => error,
767 }
768}
769
770#[inline]
781fn map_c_string_parse_error_to_decode_failure(
782 error: ParseError,
783 input: &[u8],
784 index: usize,
785) -> DecodeFailure<MiscCodecError> {
786 match error {
787 ParseError::Incomplete { required, .. } => DecodeFailure::incomplete(required),
788 ParseError::Invalid(error) => {
789 map_misc_decode_failure_with_consumed(error, c_string_invalid_consumed(input, index))
790 }
791 }
792}
793
794#[inline]
804fn c_string_invalid_consumed(input: &[u8], index: usize) -> NonZeroUsize {
805 let available = input.len().saturating_sub(index);
806 let width = match input.get(index..index.saturating_add(2)) {
807 Some([b'\\', b'u']) => 6,
808 Some([b'\\', b'U']) => 10,
809 Some([b'\\', _]) => 2,
810 _ => 1,
811 };
812 NonZeroUsize::new(width.min(available).max(1)).expect("malformed C string input has at least one unit")
813}
814
815pub(crate) fn invalid_escape(index: usize, escape: &str, reason: &str) -> MiscCodecError {
826 MiscCodecError::InvalidEscape {
827 index,
828 escape: escape.to_owned(),
829 reason: reason.to_owned(),
830 }
831}