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]
48#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
49pub struct CStringLiteralCodec;
50
51impl CStringLiteralCodec {
52 #[inline]
57 pub fn new() -> Self {
58 Self
59 }
60
61 #[inline]
69 #[must_use]
70 pub fn encode(&self, bytes: &[u8]) -> String {
71 let mut output = String::with_capacity(bytes.len());
72 for byte in bytes {
73 push_encoded_byte(*byte, &mut output);
74 }
75 output
76 }
77
78 #[inline]
93 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
94 let input = text.as_bytes();
95 let mut output = Vec::with_capacity(text.len());
96 let mut index = 0;
97 while index < input.len() {
98 let (decoded, consumed) =
99 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::CompleteText(text))
100 .map_err(|error| c_string_parse_error_to_misc(error, input, index))?;
101 debug_assert!(consumed > 0);
102 output.push(decoded);
103 index += consumed;
104 }
105 Ok(output)
106 }
107}
108
109impl ValueEncoder<[u8]> for CStringLiteralCodec {
110 type Error = MiscCodecError;
111 type Output = String;
112
113 #[inline]
115 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
116 Ok(CStringLiteralCodec::encode(self, input))
117 }
118}
119
120impl ValueDecoder<str> for CStringLiteralCodec {
121 type Error = MiscCodecError;
122 type Output = Vec<u8>;
123
124 #[inline]
126 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
127 CStringLiteralCodec::decode(self, input)
128 }
129}
130
131impl Codec for CStringLiteralCodec {
132 type Value = u8;
133 type Unit = u8;
134 type DecodeError = MiscCodecError;
135 type EncodeError = MiscCodecError;
136
137 const MIN_UNITS_PER_VALUE: usize = 1;
138 const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
139
140 const MAX_DECODE_UNITS_PER_VALUE: usize = 10;
141
142 #[inline]
144 fn encode_len(&self, value: &u8) -> usize {
145 encoded_byte_len(*value)
146 }
147
148 #[inline]
154 unsafe fn decode(
155 &mut self,
156 input: &[u8],
157 input_index: usize,
158 ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
159 debug_assert!(input_index < input.len());
160
161 let (value, consumed) = decode_c_string_literal_byte(input, input_index)
162 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
163 debug_assert!(consumed > 0);
164 let consumed = nonzero(consumed);
167 Ok((value, consumed))
168 }
169
170 #[inline]
176 unsafe fn decode_eof(
177 &mut self,
178 input: &[u8],
179 input_index: usize,
180 ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
181 debug_assert!(input_index < input.len());
182
183 let (value, consumed) = decode_c_string_literal_unit(input, input_index, CStringLiteralParseContext::EofBytes)
184 .map_err(|error| map_c_string_parse_error_to_decode_failure(error, input, input_index))?;
185 debug_assert!(consumed > 0);
186 let consumed = nonzero(consumed);
187 Ok((value, consumed))
188 }
189
190 #[inline]
196 unsafe fn encode(
197 &mut self,
198 value: &u8,
199 output: &mut [u8],
200 output_index: usize,
201 ) -> Result<usize, Self::EncodeError> {
202 let required = encoded_byte_len(*value);
203 debug_assert!(output_index + required <= output.len());
204
205 let written = write_encoded_byte(*value, output, output_index);
206 debug_assert_eq!(written, required);
207 Ok(required)
208 }
209}
210
211#[inline]
217fn push_encoded_byte(byte: u8, output: &mut String) {
218 match byte {
219 b'\'' => output.push_str("\\'"),
220 b'"' => output.push_str("\\\""),
221 b'?' => output.push_str("\\?"),
222 b'\\' => output.push_str("\\\\"),
223 0x07 => output.push_str("\\a"),
224 0x08 => output.push_str("\\b"),
225 0x0c => output.push_str("\\f"),
226 b'\n' => output.push_str("\\n"),
227 b'\r' => output.push_str("\\r"),
228 b'\t' => output.push_str("\\t"),
229 0x0b => output.push_str("\\v"),
230 b' '..=b'~' => output.push(byte as char),
231 _ => {
232 output.push('\\');
233 output.push('x');
234 output.push(uppercase_hex_digit(byte >> 4));
235 output.push(uppercase_hex_digit(byte & 0x0f));
236 }
237 }
238}
239
240#[inline]
252fn decode_c_string_literal_byte(input: &[u8], index: usize) -> Result<(u8, usize), ParseError> {
253 decode_c_string_literal_unit(input, index, CStringLiteralParseContext::StreamingBytes)
254}
255
256#[inline]
269fn decode_c_string_literal_unit(
270 input: &[u8],
271 index: usize,
272 context: CStringLiteralParseContext<'_>,
273) -> Result<(u8, usize), ParseError> {
274 debug_assert!(index < input.len());
275 let available = input.len() - index;
276 let byte = input[index];
277 if byte != b'\\' {
278 validate_source_unit(input, index, byte, context)?;
279 return Ok((byte, 1));
280 }
281 if available < 2 {
282 return Err(context.trailing_escape_error(index, available));
283 }
284 let escape = input[index + 1];
285 match escape {
286 b' ' => Ok((b' ', 2)),
287 b'\'' => Ok((b'\'', 2)),
288 b'"' => Ok((b'"', 2)),
289 b'?' => Ok((b'?', 2)),
290 b'\\' => Ok((b'\\', 2)),
291 b'a' => Ok((0x07, 2)),
292 b'b' => Ok((0x08, 2)),
293 b'f' => Ok((0x0c, 2)),
294 b'n' => Ok((b'\n', 2)),
295 b'r' => Ok((b'\r', 2)),
296 b't' => Ok((b'\t', 2)),
297 b'v' => Ok((0x0b, 2)),
298 b'x' | b'X' => {
299 if !context.is_complete_text() {
300 ensure_variable_hex_escape_complete(input, index, available)?;
301 }
302 parse_variable_hex_escape_units(input, index)
303 }
304 b'u' => {
305 if matches!(context, CStringLiteralParseContext::CompleteText(_)) || context.is_streaming() {
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(_)) || context.is_streaming() {
312 ensure_fixed_escape_complete(available, nonzero(10))?;
313 }
314 parse_fixed_hex_escape_units(input, index, 8, context)
315 }
316 b'0'..=b'7' => {
317 if context.is_streaming() {
318 ensure_octal_escape_complete(input, index, available)?;
319 }
320 Ok(parse_octal_escape_units(input, index))
321 }
322 _ => Err(invalid_escape(
323 index,
324 &context.escape_fragment(input, index, index + 2),
325 "unsupported escape sequence",
326 )
327 .into()),
328 }
329}
330
331#[inline]
341fn ensure_variable_hex_escape_complete(input: &[u8], index: usize, available: usize) -> Result<(), ParseError> {
342 if available < 3 {
343 return Err(ParseError::Incomplete { required: nonzero(3) });
344 }
345 let mut digit_count = 0usize;
346 while digit_count < 2 {
347 let Some(&byte) = input.get(index + 2 + digit_count) else {
348 break;
349 };
350 if hex_value(char::from(byte)).is_none() {
351 break;
352 }
353 digit_count += 1;
354 }
355 if digit_count == 1 && index + 3 == input.len() {
356 return Err(ParseError::Incomplete { required: nonzero(4) });
357 }
358 Ok(())
359}
360
361#[inline]
370fn ensure_fixed_escape_complete(available: usize, required: core::num::NonZeroUsize) -> Result<(), ParseError> {
371 if available < required.get() {
372 return Err(ParseError::Incomplete { required });
373 }
374 Ok(())
375}
376
377#[inline]
387fn ensure_octal_escape_complete(input: &[u8], index: usize, _available: usize) -> Result<(), ParseError> {
388 let mut digit_count = 0usize;
389 while digit_count < 3 {
390 let Some(&byte) = input.get(index + 1 + digit_count) else {
391 break;
392 };
393 if octal_value(char::from(byte)).is_none() {
394 break;
395 }
396 digit_count += 1;
397 }
398 if digit_count < 3 && index + 1 + digit_count == input.len() {
399 return Err(ParseError::Incomplete {
400 required: nonzero(2 + digit_count),
401 });
402 }
403 Ok(())
404}
405
406#[inline]
418fn validate_source_unit(
419 input: &[u8],
420 index: usize,
421 byte: u8,
422 context: CStringLiteralParseContext<'_>,
423) -> Result<(), ParseError> {
424 if matches!(byte, b'\t' | b'\n' | 0x0b | 0x0c | b' '..=b'~') {
425 return Ok(());
426 }
427 Err(MiscCodecError::InvalidCharacter {
428 index,
429 character: context.source_character(input, index),
430 reason: context.raw_source_reason().to_owned(),
431 }
432 .into())
433}
434
435#[inline]
448fn parse_variable_hex_escape_units(input: &[u8], marker_index: usize) -> Result<(u8, usize), ParseError> {
449 let mut value = 0u8;
450 let mut digit_count = 0usize;
451 let mut index = marker_index + 2;
452 while digit_count < 2 {
453 let Some(&byte) = input.get(index) else {
454 break;
455 };
456 let Some(digit) = hex_value(char::from(byte)) else {
457 break;
458 };
459 value = (value << 4) | digit;
460 index += 1;
461 digit_count += 1;
462 }
463 if digit_count == 0 {
464 return Err(invalid_escape(marker_index, "\\x", "expected at least one hexadecimal digit").into());
465 }
466 Ok((value, 2 + digit_count))
467}
468
469#[inline]
484fn parse_fixed_hex_escape_units(
485 input: &[u8],
486 marker_index: usize,
487 digits: usize,
488 context: CStringLiteralParseContext<'_>,
489) -> Result<(u8, usize), ParseError> {
490 let mut value = 0u32;
491 let mut index = marker_index + 2;
492 for _ in 0..digits {
493 let Some(_) = input.get(index) else {
494 return Err(invalid_escape(
495 marker_index,
496 &context.escape_fragment(input, marker_index, input.len()),
497 "incomplete universal character escape",
498 )
499 .into());
500 };
501 let character = context.source_character(input, index);
502 let Some(digit) = hex_value(character) else {
503 return Err(MiscCodecError::InvalidDigit {
504 radix: 16,
505 index,
506 character,
507 }
508 .into());
509 };
510 value = (value << 4) | u32::from(digit);
511 index += 1;
512 }
513 if value > u32::from(u8::MAX) {
514 return Err(invalid_escape(
515 marker_index,
516 &context.escape_fragment(input, marker_index, index),
517 "universal character value must fit in one byte",
518 )
519 .into());
520 }
521 Ok((value as u8, 2 + digits))
522}
523
524#[inline]
534fn parse_octal_escape_units(input: &[u8], marker_index: usize) -> (u8, usize) {
535 let mut value = 0u16;
536 let mut digit_count = 0usize;
537 let mut index = marker_index + 1;
538 while digit_count < 3 {
539 let Some(&byte) = input.get(index) else {
540 break;
541 };
542 let Some(digit) = octal_value(char::from(byte)) else {
543 break;
544 };
545 value = (value << 3) | u16::from(digit);
546 index += 1;
547 digit_count += 1;
548 }
549 (value as u8, 1 + digit_count)
550}
551
552#[must_use]
560#[inline]
561fn encoded_byte_len(byte: u8) -> usize {
562 match byte {
563 b'\'' | b'"' | b'?' | b'\\' | 0x07 | 0x08 | 0x0c | b'\n' | b'\r' | b'\t' | 0x0b => 2,
564 b' '..=b'~' => 1,
565 _ => 4,
566 }
567}
568
569#[inline]
579fn write_encoded_byte(byte: u8, output: &mut [u8], index: usize) -> usize {
580 match byte {
581 b'\'' => write_ascii_escape(output, index, b'\''),
582 b'"' => write_ascii_escape(output, index, b'"'),
583 b'?' => write_ascii_escape(output, index, b'?'),
584 b'\\' => write_ascii_escape(output, index, b'\\'),
585 0x07 => write_ascii_escape(output, index, b'a'),
586 0x08 => write_ascii_escape(output, index, b'b'),
587 0x0c => write_ascii_escape(output, index, b'f'),
588 b'\n' => write_ascii_escape(output, index, b'n'),
589 b'\r' => write_ascii_escape(output, index, b'r'),
590 b'\t' => write_ascii_escape(output, index, b't'),
591 0x0b => write_ascii_escape(output, index, b'v'),
592 b' '..=b'~' => {
593 output[index] = byte;
594 1
595 }
596 _ => {
597 output[index] = b'\\';
598 output[index + 1] = b'x';
599 output[index + 2] = uppercase_hex_digit(byte >> 4) as u8;
600 output[index + 3] = uppercase_hex_digit(byte & 0x0f) as u8;
601 4
602 }
603 }
604}
605
606#[inline]
616fn write_ascii_escape(output: &mut [u8], index: usize, escape: u8) -> usize {
617 output[index] = b'\\';
618 output[index + 1] = escape;
619 2
620}
621
622fn escape_fragment(input: &[u8], start: usize, end: usize) -> String {
635 let bounded_end = end.min(input.len());
636 input[start..bounded_end].iter().map(|byte| char::from(*byte)).collect()
637}
638
639#[inline]
647fn hex_value(character: char) -> Option<u8> {
648 match character {
649 '0'..='9' => Some(character as u8 - b'0'),
650 'a'..='f' => Some(character as u8 - b'a' + 10),
651 'A'..='F' => Some(character as u8 - b'A' + 10),
652 _ => None,
653 }
654}
655
656#[inline]
664fn octal_value(character: char) -> Option<u8> {
665 match character {
666 '0'..='7' => Some(character as u8 - b'0'),
667 _ => None,
668 }
669}
670
671#[inline]
679fn uppercase_hex_digit(value: u8) -> char {
680 UPPER_HEX_DIGITS[(value & 0x0f) as usize]
681}
682
683#[inline]
693fn c_string_parse_error_to_misc(error: ParseError, input: &[u8], index: usize) -> MiscCodecError {
694 match error {
695 ParseError::Incomplete { required, .. } => MiscCodecError::InvalidEscape {
696 index,
697 escape: escape_fragment(input, index, input.len()),
698 reason: format!("incomplete escape sequence; expected at least {required} units"),
699 },
700 ParseError::Invalid(error) => error,
701 }
702}
703
704#[inline]
706fn map_c_string_parse_error_to_decode_failure(
707 error: ParseError,
708 input: &[u8],
709 index: usize,
710) -> DecodeFailure<MiscCodecError> {
711 match error {
712 ParseError::Incomplete { required, .. } => DecodeFailure::incomplete(required),
713 ParseError::Invalid(error) => {
714 map_misc_decode_failure_with_consumed(error, c_string_invalid_consumed(input, index))
715 }
716 }
717}
718
719#[inline]
721fn c_string_invalid_consumed(input: &[u8], index: usize) -> NonZeroUsize {
722 let available = input.len().saturating_sub(index);
723 let width = match input.get(index..index.saturating_add(2)) {
724 Some([b'\\', b'u']) => 6,
725 Some([b'\\', b'U']) => 10,
726 Some([b'\\', _]) => 2,
727 _ => 1,
728 };
729 NonZeroUsize::new(width.min(available).max(1)).expect("malformed C string input has at least one unit")
730}
731
732pub(crate) fn invalid_escape(index: usize, escape: &str, reason: &str) -> MiscCodecError {
743 MiscCodecError::InvalidEscape {
744 index,
745 escape: escape.to_owned(),
746 reason: reason.to_owned(),
747 }
748}