Skip to main content

ass_core/utils/
uu.rs

1//! UU-encoded binary data decoding for ASS `[Fonts]`/`[Graphics]` sections.
2//!
3//! Decodes Unix-to-Unix encoded binary payloads embedded as ASCII text.
4
5#[cfg(not(feature = "std"))]
6use alloc::vec::Vec;
7#[cfg(feature = "std")]
8use std::vec::Vec;
9
10use super::CoreError;
11
12/// Decode UU-encoded data commonly found in ASS `[Fonts]` and `[Graphics]` sections
13///
14/// UU-encoding (Unix-to-Unix encoding) embeds binary data as ASCII text.
15/// Each line starts with a length character followed by encoded data.
16///
17/// # Arguments
18///
19/// * `lines` - Iterator of UU-encoded text lines
20///
21/// # Returns
22///
23/// Decoded binary data or error if encoding is invalid.
24///
25/// # Example
26///
27/// ```rust
28/// # use ass_core::utils::decode_uu_data;
29/// let lines = vec![""];
30/// let decoded = decode_uu_data(lines.iter().map(|s| *s))?;
31/// // UU-decode implementation handles empty input gracefully
32/// assert!(decoded.len() >= 0);
33/// # Ok::<(), Box<dyn std::error::Error>>(())
34/// ```
35///
36/// # Errors
37///
38/// Returns an error if the UU-encoded data is malformed or cannot be decoded.
39#[allow(clippy::similar_names)]
40pub fn decode_uu_data<'a, I>(lines: I) -> Result<Vec<u8>, CoreError>
41where
42    I: Iterator<Item = &'a str>,
43{
44    let mut result = Vec::new();
45
46    for line in lines {
47        let line = line.trim_start().trim_end_matches(['\n', '\r']);
48        if line.is_empty() {
49            continue;
50        }
51
52        // Check for end marker
53        if line == "end" || line.starts_with("end ") {
54            break;
55        }
56
57        let input_bytes = line.as_bytes();
58        if input_bytes.is_empty() {
59            continue;
60        }
61
62        // First character encodes the line length
63        let expected_length = (input_bytes[0].wrapping_sub(b' ')) as usize;
64
65        // Only process lines with reasonable UU length values (0-45)
66        // This filters out obvious non-UU lines like comments
67        if expected_length > 45 {
68            continue;
69        }
70
71        // If length is 0, this indicates end of data
72        if expected_length == 0 {
73            break;
74        }
75
76        let data_part = &input_bytes[1..];
77        let mut decoded_bytes = Vec::new();
78
79        // Process groups of 4 characters into 3 bytes
80        for chunk in data_part.chunks(4) {
81            let mut group = [b' '; 4];
82            for (i, &byte) in chunk.iter().enumerate() {
83                group[i] = byte;
84            }
85
86            // Decode 4 characters to 3 bytes
87            let c1 = group[0].wrapping_sub(b' ');
88            let c2 = group[1].wrapping_sub(b' ');
89            let c3 = group[2].wrapping_sub(b' ');
90            let c4 = group[3].wrapping_sub(b' ');
91
92            let decoded_byte1 = (c1 << 2) | (c2 >> 4);
93            let decoded_byte2 = ((c2 & 0x0F) << 4) | (c3 >> 2);
94            let decoded_byte3 = ((c3 & 0x03) << 6) | c4;
95
96            // Always decode all 3 bytes - missing chars are treated as spaces (value 0)
97            decoded_bytes.push(decoded_byte1);
98            decoded_bytes.push(decoded_byte2);
99            decoded_bytes.push(decoded_byte3);
100        }
101
102        // Truncate to expected length to handle padding
103        decoded_bytes.truncate(expected_length);
104        result.extend_from_slice(&decoded_bytes);
105    }
106    Ok(result)
107}