Skip to main content

rar_stream/parsing/
marker_header.rs

1//! Marker header parser - RAR signature.
2//!
3//! The marker header is the first 7 bytes of a RAR file.
4//! RAR4: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00
5//! RAR5: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00
6
7use crate::error::{RarError, Result};
8
9/// RAR4 magic signature.
10pub const RAR4_SIGNATURE: [u8; 7] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00];
11
12/// RAR5 magic signature.
13pub const RAR5_SIGNATURE: [u8; 8] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00];
14
15/// RAR archive version.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum RarVersion {
18    /// RAR 4.x format (also known as RAR 2.9)
19    #[default]
20    Rar4,
21    /// RAR 5.x format
22    Rar5,
23}
24
25impl RarVersion {
26    /// Returns the signature size for this version.
27    pub fn signature_size(&self) -> usize {
28        match self {
29            Self::Rar4 => 7,
30            Self::Rar5 => 8,
31        }
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct MarkerHeader {
37    pub crc: u16,
38    pub header_type: u8,
39    pub flags: u16,
40    pub size: u32,
41    pub version: RarVersion,
42}
43
44pub struct MarkerHeaderParser;
45
46impl MarkerHeaderParser {
47    pub const HEADER_SIZE: usize = 7;
48
49    /// Detect RAR version from buffer without full parsing.
50    pub fn detect_version(buffer: &[u8]) -> Result<RarVersion> {
51        if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
52            return Ok(RarVersion::Rar5);
53        }
54        if buffer.len() >= 7 && buffer[..7] == RAR4_SIGNATURE {
55            return Ok(RarVersion::Rar4);
56        }
57        Err(RarError::InvalidSignature)
58    }
59
60    /// Parse marker header from buffer.
61    /// The marker header is actually parsed as a generic RAR header.
62    /// The "size" field tells us how many bytes this header consumes.
63    pub fn parse(buffer: &[u8]) -> Result<MarkerHeader> {
64        if buffer.len() < Self::HEADER_SIZE {
65            return Err(RarError::BufferTooSmall {
66                needed: Self::HEADER_SIZE,
67                have: buffer.len(),
68            });
69        }
70
71        // Check for RAR5 first (longer signature)
72        if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
73            return Ok(MarkerHeader {
74                crc: 0,
75                header_type: 0,
76                flags: 0,
77                size: 8,
78                version: RarVersion::Rar5,
79            });
80        }
81
82        // Verify RAR4 signature (first 7 bytes)
83        if buffer[..7] != RAR4_SIGNATURE {
84            return Err(RarError::InvalidSignature);
85        }
86
87        // Parse as generic header structure (RAR4)
88        let crc = u16::from_le_bytes([buffer[0], buffer[1]]);
89        let header_type = buffer[2];
90        let flags = u16::from_le_bytes([buffer[3], buffer[4]]);
91        let size = u16::from_le_bytes([buffer[5], buffer[6]]) as u32;
92
93        Ok(MarkerHeader {
94            crc,
95            header_type,
96            flags,
97            size,
98            version: RarVersion::Rar4,
99        })
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_parse_rar4_marker() {
109        // RAR4 signature + header
110        let buffer = [
111            0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00, // RAR4 signature
112            0x00, 0x00, 0x00, 0x00, // padding for HEADER_SIZE
113        ];
114        let header = MarkerHeaderParser::parse(&buffer).unwrap();
115        assert_eq!(header.header_type, b'r'); // 0x72
116    }
117
118    #[test]
119    fn test_invalid_signature() {
120        let buffer = [0x00; 11];
121        assert!(matches!(
122            MarkerHeaderParser::parse(&buffer),
123            Err(RarError::InvalidSignature)
124        ));
125    }
126
127    #[test]
128    fn test_buffer_too_small() {
129        let buffer = [0x52, 0x61, 0x72];
130        assert!(matches!(
131            MarkerHeaderParser::parse(&buffer),
132            Err(RarError::BufferTooSmall { .. })
133        ));
134    }
135}