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) =
89 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::CompleteText(text))
90 .map_err(|error| c_string_parse_error_to_misc(error, input, index))?;
91 debug_assert!(consumed > 0);
92 output.push(decoded);
93 index += consumed;
94 }
95 Ok(output)
96 }
97}
98
99impl ValueEncoder<[u8]> for CStringLiteralCodec {
100 type Error = MiscCodecError;
101 type Output = String;
102
103 #[inline]
105 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
106 Ok(CStringLiteralCodec::encode(self, input))
107 }
108}
109
110impl ValueDecoder<str> for CStringLiteralCodec {
111 type Error = MiscCodecError;
112 type Output = Vec<u8>;
113
114 #[inline]
116 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
117 CStringLiteralCodec::decode(self, input)
118 }
119}
120
121impl Codec for CStringLiteralCodec {
122 type Value = u8;
123 type Unit = u8;
124 type DecodeError = MiscCodecError;
125 type EncodeError = MiscCodecError;
126
127 const MIN_UNITS_PER_VALUE: usize = 1;
128 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
129
130 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
131
132 #[inline(always)]
134 fn encode_len(&self, value: &u8) -> usize {
135 encoded_byte_len(*value)
136 }
137
138 #[inline]
144 unsafe fn decode(
145 &mut self,
146 input: &[u8],
147 input_index: usize,
148 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
149 debug_assert!(input_index < input.len());
150
151 let (value, consumed) = decode_c_string_literal_byte(input, input_index)
152 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
153 debug_assert!(consumed > 0);
154 let consumed = nonzero(consumed);
157 Ok((value, consumed))
158 }
159
160 #[inline]
166 unsafe fn decode_eof(
167 &mut self,
168 input: &[u8],
169 input_index: usize,
170 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
171 debug_assert!(input_index < input.len());
172
173 let (value, consumed) = decode_c_string_literal_unit(input, input_index, CStringLiteralParseContext::EofBytes)
174 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
175 debug_assert!(consumed > 0);
176 let consumed = nonzero(consumed);
177 Ok((value, consumed))
178 }
179
180 #[inline]
186 unsafe fn encode(
187 &mut self,
188 value: &u8,
189 output: &mut [u8],
190 output_index: usize,
191 ) -> Result<usize, Self::EncodeError> {
192 let required = encoded_byte_len(*value);
193 debug_assert!(output_index + required <= output.len());
194
195 let written = write_encoded_byte(*value, output, output_index);
196 debug_assert_eq!(written, required);
197 Ok(required)
198 }
199}
200
201#[inline]
207fn push_encoded_byte(byte: u8, output: &mut String) {
208 match byte {
209 b'\'' => output.push_str("\\'"),
210 b'"' => output.push_str("\\\""),
211 b'?' => output.push_str("\\?"),
212 b'\\' => output.push_str("\\\\"),
213 0x07 => output.push_str("\\a"),
214 0x08 => output.push_str("\\b"),
215 0x0c => output.push_str("\\f"),
216 b'\n' => output.push_str("\\n"),
217 b'\r' => output.push_str("\\r"),
218 b'\t' => output.push_str("\\t"),
219 0x0b => output.push_str("\\v"),
220 b' '..=b'~' => output.push(byte as char),
221 _ => {
222 output.push('\\');
223 output.push('x');
224 output.push(uppercase_hex_digit(byte >> 4));
225 output.push(uppercase_hex_digit(byte & 0x0f));
226 }
227 }
228}
229
230#[inline]
242fn decode_c_string_literal_byte(input: &[u8], index: usize) -> Result<(u8, usize), ParseError> {
243 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::StreamingBytes)
244}
245
246#[inline]
259fn decode_c_string_literal_unit(
260 input: &[u8],
261 index: usize,
262 context: CStringLiteralParseContext<'_>,
263) -> Result<(u8, usize), ParseError> {
264 debug_assert!(index < input.len());
265 let available = input.len() - index;
266 let byte = input[index];
267 if byte != b'\\' {
268 validate_source_unit(input, index, byte, context)?;
269 return Ok((byte, 1));
270 }
271 if available < 2 {
272 return Err(context.trailing_escape_error(index, available));
273 }
274 let escape = input[index + 1];
275 match escape {
276 b' ' => Ok((b' ', 2)),
277 b'\'' => Ok((b'\'', 2)),
278 b'"' => Ok((b'"', 2)),
279 b'?' => Ok((b'?', 2)),
280 b'\\' => Ok((b'\\', 2)),
281 b'a' => Ok((0x07, 2)),
282 b'b' => Ok((0x08, 2)),
283 b'f' => Ok((0x0c, 2)),
284 b'n' => Ok((b'\n', 2)),
285 b'r' => Ok((b'\r', 2)),
286 b't' => Ok((b'\t', 2)),
287 b'v' => Ok((0x0b, 2)),
288 b'x' | b'X' => {
289 if !context.is_complete_text() {
290 ensure_variable_hex_escape_complete(input, index, available)?;
291 }
292 parse_variable_hex_escape_units(input, index)
293 }
294 b'u' => {
295 if matches!(context, CStringLiteralParseContext::CompleteText(_)) || context.is_streaming() {
296 ensure_fixed_escape_complete(available, nonzero(6))?;
297 }
298 parse_fixed_hex_escape_units(input, index, 4, context)
299 }
300 b'U' => {
301 if matches!(context, CStringLiteralParseContext::CompleteText(_)) || context.is_streaming() {
302 ensure_fixed_escape_complete(available, nonzero(10))?;
303 }
304 parse_fixed_hex_escape_units(input, index, 8, context)
305 }
306 b'0'..=b'7' => {
307 if context.is_streaming() {
308 ensure_octal_escape_complete(input, index, available)?;
309 }
310 Ok(parse_octal_escape_units(input, index))
311 }
312 _ => Err(invalid_escape(
313 index,
314 &context.escape_fragment(input, index, index + 2),
315 "unsupported escape sequence",
316 )
317 .into()),
318 }
319}
320
321#[inline]
331fn ensure_variable_hex_escape_complete(input: &[u8], index: usize, available: usize) -> Result<(), ParseError> {
332 if available < 3 {
333 return Err(ParseError::Incomplete { required: nonzero(3) });
334 }
335 let mut digit_count = 0usize;
336 while digit_count < 2 {
337 let Some(&byte) = input.get(index + 2 + digit_count) else {
338 break;
339 };
340 if hex_value(char::from(byte)).is_none() {
341 break;
342 }
343 digit_count += 1;
344 }
345 if digit_count == 1 && index + 3 == input.len() {
346 return Err(ParseError::Incomplete { required: nonzero(4) });
347 }
348 Ok(())
349}
350
351#[inline]
360fn ensure_fixed_escape_complete(available: usize, required: core::num::NonZeroUsize) -> Result<(), ParseError> {
361 if available < required.get() {
362 return Err(ParseError::Incomplete { required });
363 }
364 Ok(())
365}
366
367#[inline]
377fn ensure_octal_escape_complete(input: &[u8], index: usize, _available: usize) -> Result<(), ParseError> {
378 let mut digit_count = 0usize;
379 while digit_count < 3 {
380 let Some(&byte) = input.get(index + 1 + digit_count) else {
381 break;
382 };
383 if octal_value(char::from(byte)).is_none() {
384 break;
385 }
386 digit_count += 1;
387 }
388 if digit_count < 3 && index + 1 + digit_count == input.len() {
389 return Err(ParseError::Incomplete {
390 required: nonzero(2 + digit_count),
391 });
392 }
393 Ok(())
394}
395
396#[inline]
408fn validate_source_unit(
409 input: &[u8],
410 index: usize,
411 byte: u8,
412 context: CStringLiteralParseContext<'_>,
413) -> Result<(), ParseError> {
414 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
415 return Ok(());
416 }
417 Err(MiscCodecError::InvalidCharacter {
418 index,
419 character: context.source_character(input, index),
420 reason: context.raw_source_reason().to_owned(),
421 }
422 .into())
423}
424
425#[inline]
438fn parse_variable_hex_escape_units(input: &[u8], marker_index: usize) -> Result<(u8, usize), ParseError> {
439 let mut value = 0u8;
440 let mut digit_count = 0usize;
441 let mut index = marker_index + 2;
442 while digit_count < 2 {
443 let Some(&byte) = input.get(index) else {
444 break;
445 };
446 let Some(digit) = hex_value(char::from(byte)) else {
447 break;
448 };
449 value = (value << 4) | digit;
450 index += 1;
451 digit_count += 1;
452 }
453 if digit_count == 0 {
454 return Err(invalid_escape(marker_index, "\\x", "expected at least one hexadecimal digit").into());
455 }
456 Ok((value, 2 + digit_count))
457}
458
459#[inline]
474fn parse_fixed_hex_escape_units(
475 input: &[u8],
476 marker_index: usize,
477 digits: usize,
478 context: CStringLiteralParseContext<'_>,
479) -> Result<(u8, usize), ParseError> {
480 let mut value = 0u32;
481 let mut index = marker_index + 2;
482 for _ in 0..digits {
483 let Some(_) = input.get(index) else {
484 return Err(invalid_escape(
485 marker_index,
486 &context.escape_fragment(input, marker_index, input.len()),
487 "incomplete universal character escape",
488 )
489 .into());
490 };
491 let character = context.source_character(input, index);
492 let Some(digit) = hex_value(character) else {
493 return Err(MiscCodecError::InvalidDigit {
494 radix: 16,
495 index,
496 character,
497 }
498 .into());
499 };
500 value = (value << 4) | u32::from(digit);
501 index += 1;
502 }
503 if value > u32::from(u8::MAX) {
504 return Err(invalid_escape(
505 marker_index,
506 &context.escape_fragment(input, marker_index, index),
507 "universal character value must fit in one byte",
508 )
509 .into());
510 }
511 Ok((value as u8, 2 + digits))
512}
513
514#[inline]
524fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
525 let mut value = 0u16;
526 let mut digit_count = 0usize;
527 let mut index = marker_index + 1;
528 while digit_count < 3 {
529 let Some(&byte) = input.get(index) else {
530 break;
531 };
532 let Some(digit) = octal_value(char::from(byte)) else {
533 break;
534 };
535 value = (value << 3) | u16::from(digit);
536 index += 1;
537 digit_count += 1;
538 }
539 (value as u8, 1 + digit_count)
540}
541
542#[must_use]
550#[inline(always)]
551fn encoded_byte_len(byte: u8) -> usize {
552 match byte {
553 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r' | b'\t' | 0x0b => 2,
554 b' '..=b'~' => 1,
555 _ => 4,
556 }
557}
558
559#[inline]
569fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
570 match byte {
571 b'\'' => write_ascii_escape(output, index, b'\''),
572 b'"' => write_ascii_escape(output, index, b'"'),
573 b'?' => write_ascii_escape(output, index, b'?'),
574 b'\\' => write_ascii_escape(output, index, b'\\'),
575 0x07 => write_ascii_escape(output, index, b'a'),
576 0x08 => write_ascii_escape(output, index, b'b'),
577 0x0c => write_ascii_escape(output, index, b'f'),
578 b'\n' => write_ascii_escape(output, index, b'n'),
579 b'\r' => write_ascii_escape(output, index, b'r'),
580 b'\t' => write_ascii_escape(output, index, b't'),
581 0x0b => write_ascii_escape(output, index, b'v'),
582 b' '..=b'~' => {
583 output[index] = byte;
584 1
585 }
586 _ => {
587 output[index] = b'\\';
588 output[index + 1] = b'x';
589 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
590 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
591 4
592 }
593 }
594}
595
596#[inline(always)]
606fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
607 output[index] = b'\\';
608 output[index + 1] = escape;
609 2
610}
611
612fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
622 let bounded_end = end.min(input.len());
623 input[start..bounded_end].iter().map(|byte| char::from(*byte)).collect()
624}
625
626#[inline(always)]
634fn hex_value(character: char) -> Option<u8> {
635 match character {
636 '0'..='9' => Some(character as u8 - b'0'),
637 'a'..='f' => Some(character as u8 - b'a' + 10),
638 'A'..='F' => Some(character as u8 - b'A' + 10),
639 _ => None,
640 }
641}
642
643#[inline(always)]
651fn octal_value(character: char) -> Option<u8> {
652 match character {
653 '0'..='7' => Some(character as u8 - b'0'),
654 _ => None,
655 }
656}
657
658#[inline(always)]
666fn uppercase_hex_digit(value: u8) -> char {
667 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
668}
669
670#[inline]
680fn c_string_parse_error_to_misc(error: ParseError, input: &[u8], index: usize) -> MiscCodecError {
681 match error {
682 ParseError::Incomplete { required, .. } => MiscCodecError::InvalidEscape {
683 index,
684 escape: escape_fragment(input, index, input.len()),
685 reason: format!("incomplete escape sequence; expected at least {required} units"),
686 },
687 ParseError::Invalid(error) => error,
688 }
689}
690
691#[inline]
693fn map_c_string_parse_error_to_decode_failure(
694 error: ParseError,
695 input: &[u8],
696 index: usize,
697) -> DecodeFailure<MiscCodecError> {
698 match error {
699 ParseError::Incomplete { required, .. } => DecodeFailure::incomplete(required),
700 ParseError::Invalid(error) => {
701 map_misc_decode_failure_with_consumed(error, c_string_invalid_consumed(input, index))
702 }
703 }
704}
705
706#[inline]
708fn c_string_invalid_consumed(input: &[u8], index: usize) -> NonZeroUsize {
709 let available = input.len().saturating_sub(index);
710 let width = match input.get(index..index.saturating_add(2)) {
711 Some([b'\\', b'u']) => 6,
712 Some([b'\\', b'U']) => 10,
713 Some([b'\\', _]) => 2,
714 _ => 1,
715 };
716 NonZeroUsize::new(width.min(available).max(1)).expect("malformed C string input has at least one unit")
717}
718
719pub(crate) fn invalid_escape(index: usize, escape: &str, reason: &str) -> MiscCodecError {
729 MiscCodecError::InvalidEscape {
730 index,
731 escape: escape.to_owned(),
732 reason: reason.to_owned(),
733 }
734}