1use crate::{
11 MiscCodecError,
12 MiscCodecResult,
13 internal::{
14 CStringLiteralParseContext,
15 ParseError,
16 },
17 misc_codec_error::map_misc_decode_failure_with_consumed,
18};
19use qubit_codec::{
20 Codec,
21 ValueDecoder,
22 ValueEncoder,
23};
24
25const UPPER_HEX_DIGITS: [char; 16] = [
26 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
27 'F',
28];
29
30#[must_use]
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
41pub struct CStringLiteralCodec;
42
43impl CStringLiteralCodec {
44 #[inline]
49 pub fn new() -> Self {
50 Self
51 }
52
53 #[inline]
61 #[must_use]
62 pub fn encode(&self, bytes: &[u8]) -> String {
63 let mut output = String::with_capacity(bytes.len());
64 for byte in bytes {
65 push_encoded_byte(*byte, &mut output);
66 }
67 output
68 }
69
70 #[inline]
85 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
86 let input = text.as_bytes();
87 let mut output = Vec::with_capacity(text.len());
88 let mut index = 0;
89 while index < input.len() {
90 let (decoded, consumed) = decode_c_string_literal_unit(
91 input,
92 index,
93 CStringLiteralParseContext::CompleteText(text),
94 )
95 .map_err(|error| {
96 c_string_parse_error_to_misc(error, input, index)
97 })?;
98 debug_assert!(consumed > 0);
99 output.push(decoded);
100 index += consumed;
101 }
102 Ok(output)
103 }
104}
105
106impl ValueEncoder<[u8]> for CStringLiteralCodec {
107 type Error = MiscCodecError;
108 type Output = String;
109
110 #[inline]
112 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
113 Ok(CStringLiteralCodec::encode(self, input))
114 }
115}
116
117impl ValueDecoder<str> for CStringLiteralCodec {
118 type Error = MiscCodecError;
119 type Output = Vec<u8>;
120
121 #[inline]
123 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
124 CStringLiteralCodec::decode(self, input)
125 }
126}
127
128impl Codec for CStringLiteralCodec {
129 type Value = u8;
130 type Unit = u8;
131 type DecodeError = MiscCodecError;
132 type EncodeError = MiscCodecError;
133
134 const MIN_UNITS_PER_VALUE: usize = 1;
135 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
136
137 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
138
139 #[inline(always)]
141 fn encode_len(&self, value: &u8) -> usize {
142 encoded_byte_len(*value)
143 }
144
145 #[inline]
151 unsafe fn decode(
152 &mut self,
153 input: &[u8],
154 input_index: usize,
155 ) -> Result<
156 (u8, core::num::NonZeroUsize),
157 qubit_codec::DecodeFailure<Self::DecodeError>,
158 > {
159 debug_assert!(input_index < input.len());
160
161 let (value, consumed) =
162 decode_c_string_literal_byte(input, input_index).map_err(
163 |error| {
164 map_c_string_parse_error_to_decode_failure(
165 error,
166 input,
167 input_index,
168 )
169 },
170 )?;
171 debug_assert!(consumed > 0);
172 let consumed = qubit_codec::nz!(consumed);
175 Ok((value, consumed))
176 }
177
178 #[inline]
184 unsafe fn decode_eof(
185 &mut self,
186 input: &[u8],
187 input_index: usize,
188 ) -> Result<
189 (u8, core::num::NonZeroUsize),
190 qubit_codec::DecodeFailure<Self::DecodeError>,
191 > {
192 debug_assert!(input_index < input.len());
193
194 let (value, consumed) = decode_c_string_literal_unit(
195 input,
196 input_index,
197 CStringLiteralParseContext::EofBytes,
198 )
199 .map_err(|error| {
200 map_c_string_parse_error_to_decode_failure(
201 error,
202 input,
203 input_index,
204 )
205 })?;
206 debug_assert!(consumed > 0);
207 let consumed = qubit_codec::nz!(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(
274 input: &[u8],
275 index: usize,
276) -> Result<(u8, usize), ParseError> {
277 decode_c_string_literal_unit(
278 input,
279 index,
280 CStringLiteralParseContext::StreamingBytes,
281 )
282}
283
284#[inline]
297fn decode_c_string_literal_unit(
298 input: &[u8],
299 index: usize,
300 context: CStringLiteralParseContext<'_>,
301) -> Result<(u8, usize), ParseError> {
302 debug_assert!(index < input.len());
303 let available = input.len() - index;
304 let byte = input[index];
305 if byte != b'\\' {
306 validate_source_unit(input, index, byte, context)?;
307 return Ok((byte, 1));
308 }
309 if available < 2 {
310 return Err(context.trailing_escape_error(index, available));
311 }
312 let escape = input[index + 1];
313 match escape {
314 b' ' => Ok((b' ', 2)),
315 b'\'' => Ok((b'\'', 2)),
316 b'"' => Ok((b'"', 2)),
317 b'?' => Ok((b'?', 2)),
318 b'\\' => Ok((b'\\', 2)),
319 b'a' => Ok((0x07, 2)),
320 b'b' => Ok((0x08, 2)),
321 b'f' => Ok((0x0c, 2)),
322 b'n' => Ok((b'\n', 2)),
323 b'r' => Ok((b'\r', 2)),
324 b't' => Ok((b'\t', 2)),
325 b'v' => Ok((0x0b, 2)),
326 b'x' | b'X' => {
327 if !context.is_complete_text() {
328 ensure_variable_hex_escape_complete(input, index, available)?;
329 }
330 parse_variable_hex_escape_units(input, index)
331 }
332 b'u' => {
333 if matches!(context, CStringLiteralParseContext::CompleteText(_))
334 || context.is_streaming()
335 {
336 ensure_fixed_escape_complete(available, qubit_codec::nz!(6))?;
337 }
338 parse_fixed_hex_escape_units(input, index, 4, context)
339 }
340 b'U' => {
341 if matches!(context, CStringLiteralParseContext::CompleteText(_))
342 || context.is_streaming()
343 {
344 ensure_fixed_escape_complete(available, qubit_codec::nz!(10))?;
345 }
346 parse_fixed_hex_escape_units(input, index, 8, context)
347 }
348 b'0'..=b'7' => {
349 if context.is_streaming() {
350 ensure_octal_escape_complete(input, index, available)?;
351 }
352 Ok(parse_octal_escape_units(input, index))
353 }
354 _ => Err(invalid_escape(
355 index,
356 &context.escape_fragment(input, index, index + 2),
357 "unsupported escape sequence",
358 )
359 .into()),
360 }
361}
362
363#[inline]
373fn ensure_variable_hex_escape_complete(
374 input: &[u8],
375 index: usize,
376 available: usize,
377) -> Result<(), ParseError> {
378 if available < 3 {
379 return Err(ParseError::Incomplete {
380 required: qubit_codec::nz!(3),
381 });
382 }
383 let mut digit_count = 0usize;
384 while digit_count < 2 {
385 let Some(&byte) = input.get(index + 2 + digit_count) else {
386 break;
387 };
388 if hex_value(char::from(byte)).is_none() {
389 break;
390 }
391 digit_count += 1;
392 }
393 if digit_count == 1 && index + 3 == input.len() {
394 return Err(ParseError::Incomplete {
395 required: qubit_codec::nz!(4),
396 });
397 }
398 Ok(())
399}
400
401#[inline]
410fn ensure_fixed_escape_complete(
411 available: usize,
412 required: core::num::NonZeroUsize,
413) -> Result<(), ParseError> {
414 if available < required.get() {
415 return Err(ParseError::Incomplete { required });
416 }
417 Ok(())
418}
419
420#[inline]
430fn ensure_octal_escape_complete(
431 input: &[u8],
432 index: usize,
433 _available: usize,
434) -> Result<(), ParseError> {
435 let mut digit_count = 0usize;
436 while digit_count < 3 {
437 let Some(&byte) = input.get(index + 1 + digit_count) else {
438 break;
439 };
440 if octal_value(char::from(byte)).is_none() {
441 break;
442 }
443 digit_count += 1;
444 }
445 if digit_count < 3 && index + 1 + digit_count == input.len() {
446 return Err(ParseError::Incomplete {
447 required: qubit_codec::nz!(2 + digit_count),
448 });
449 }
450 Ok(())
451}
452
453#[inline]
465fn validate_source_unit(
466 input: &[u8],
467 index: usize,
468 byte: u8,
469 context: CStringLiteralParseContext<'_>,
470) -> Result<(), ParseError> {
471 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
472 return Ok(());
473 }
474 Err(MiscCodecError::InvalidCharacter {
475 index,
476 character: context.source_character(input, index),
477 reason: context.raw_source_reason().to_owned(),
478 }
479 .into())
480}
481
482#[inline]
495fn parse_variable_hex_escape_units(
496 input: &[u8],
497 marker_index: usize,
498) -> Result<(u8, usize), ParseError> {
499 let mut value = 0u8;
500 let mut digit_count = 0usize;
501 let mut index = marker_index + 2;
502 while digit_count < 2 {
503 let Some(&byte) = input.get(index) else {
504 break;
505 };
506 let Some(digit) = hex_value(char::from(byte)) else {
507 break;
508 };
509 value = (value << 4) | digit;
510 index += 1;
511 digit_count += 1;
512 }
513 if digit_count == 0 {
514 return Err(invalid_escape(
515 marker_index,
516 "\\x",
517 "expected at least one hexadecimal digit",
518 )
519 .into());
520 }
521 Ok((value, 2 + digit_count))
522}
523
524#[inline]
539fn parse_fixed_hex_escape_units(
540 input: &[u8],
541 marker_index: usize,
542 digits: usize,
543 context: CStringLiteralParseContext<'_>,
544) -> Result<(u8, usize), ParseError> {
545 let mut value = 0u32;
546 let mut index = marker_index + 2;
547 for _ in 0..digits {
548 let Some(_) = input.get(index) else {
549 return Err(invalid_escape(
550 marker_index,
551 &context.escape_fragment(input, marker_index, input.len()),
552 "incomplete universal character escape",
553 )
554 .into());
555 };
556 let character = context.source_character(input, index);
557 let Some(digit) = hex_value(character) else {
558 return Err(MiscCodecError::InvalidDigit {
559 radix: 16,
560 index,
561 character,
562 }
563 .into());
564 };
565 value = (value << 4) | u32::from(digit);
566 index += 1;
567 }
568 if value > u32::from(u8::MAX) {
569 return Err(invalid_escape(
570 marker_index,
571 &context.escape_fragment(input, marker_index, index),
572 "universal character value must fit in one byte",
573 )
574 .into());
575 }
576 Ok((value as u8, 2 + digits))
577}
578
579#[inline]
589fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
590 let mut value = 0u16;
591 let mut digit_count = 0usize;
592 let mut index = marker_index + 1;
593 while digit_count < 3 {
594 let Some(&byte) = input.get(index) else {
595 break;
596 };
597 let Some(digit) = octal_value(char::from(byte)) else {
598 break;
599 };
600 value = (value << 3) | u16::from(digit);
601 index += 1;
602 digit_count += 1;
603 }
604 (value as u8, 1 + digit_count)
605}
606
607#[must_use]
615#[inline(always)]
616fn encoded_byte_len(byte: u8) -> usize {
617 match byte {
618 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r'
619 | b'\t' | 0x0b => 2,
620 b' '..=b'~' => 1,
621 _ => 4,
622 }
623}
624
625#[inline]
635fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
636 match byte {
637 b'\'' => write_ascii_escape(output, index, b'\''),
638 b'"' => write_ascii_escape(output, index, b'"'),
639 b'?' => write_ascii_escape(output, index, b'?'),
640 b'\\' => write_ascii_escape(output, index, b'\\'),
641 0x07 => write_ascii_escape(output, index, b'a'),
642 0x08 => write_ascii_escape(output, index, b'b'),
643 0x0c => write_ascii_escape(output, index, b'f'),
644 b'\n' => write_ascii_escape(output, index, b'n'),
645 b'\r' => write_ascii_escape(output, index, b'r'),
646 b'\t' => write_ascii_escape(output, index, b't'),
647 0x0b => write_ascii_escape(output, index, b'v'),
648 b' '..=b'~' => {
649 output[index] = byte;
650 1
651 }
652 _ => {
653 output[index] = b'\\';
654 output[index + 1] = b'x';
655 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
656 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
657 4
658 }
659 }
660}
661
662#[inline(always)]
672fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
673 output[index] = b'\\';
674 output[index + 1] = escape;
675 2
676}
677
678fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
688 let bounded_end = end.min(input.len());
689 input[start..bounded_end]
690 .iter()
691 .map(|byte| char::from(*byte))
692 .collect()
693}
694
695#[inline(always)]
703fn hex_value(character: char) -> Option<u8> {
704 match character {
705 '0'..='9' => Some(character as u8 - b'0'),
706 'a'..='f' => Some(character as u8 - b'a' + 10),
707 'A'..='F' => Some(character as u8 - b'A' + 10),
708 _ => None,
709 }
710}
711
712#[inline(always)]
720fn octal_value(character: char) -> Option<u8> {
721 match character {
722 '0'..='7' => Some(character as u8 - b'0'),
723 _ => None,
724 }
725}
726
727#[inline(always)]
735fn uppercase_hex_digit(value: u8) -> char {
736 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
737}
738
739#[inline]
749fn c_string_parse_error_to_misc(
750 error: ParseError,
751 input: &[u8],
752 index: usize,
753) -> MiscCodecError {
754 match error {
755 ParseError::Incomplete { required, .. } => {
756 MiscCodecError::InvalidEscape {
757 index,
758 escape: escape_fragment(input, index, input.len()),
759 reason: format!(
760 "incomplete escape sequence; expected at least {required} units"
761 ),
762 }
763 }
764 ParseError::Invalid(error) => error,
765 }
766}
767
768#[inline]
770fn map_c_string_parse_error_to_decode_failure(
771 error: ParseError,
772 input: &[u8],
773 index: usize,
774) -> qubit_codec::DecodeFailure<MiscCodecError> {
775 match error {
776 ParseError::Incomplete { required, .. } => {
777 qubit_codec::DecodeFailure::incomplete(required)
778 }
779 ParseError::Invalid(error) => map_misc_decode_failure_with_consumed(
780 error,
781 c_string_invalid_consumed(input, index),
782 ),
783 }
784}
785
786#[inline]
788fn c_string_invalid_consumed(
789 input: &[u8],
790 index: usize,
791) -> core::num::NonZeroUsize {
792 let available = input.len().saturating_sub(index);
793 let width = match input.get(index..index.saturating_add(2)) {
794 Some([b'\\', b'u']) => 6,
795 Some([b'\\', b'U']) => 10,
796 Some([b'\\', _]) => 2,
797 _ => 1,
798 };
799 core::num::NonZeroUsize::new(width.min(available).max(1))
800 .expect("malformed C string input has at least one unit")
801}
802
803pub(crate) fn invalid_escape(
813 index: usize,
814 escape: &str,
815 reason: &str,
816) -> MiscCodecError {
817 MiscCodecError::InvalidEscape {
818 index,
819 escape: escape.to_owned(),
820 reason: reason.to_owned(),
821 }
822}