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: `Rar!\x1a\x07\x00`.
10///
11/// ```
12/// use rar_stream::parsing::marker_header::RAR4_SIGNATURE;
13/// assert_eq!(&RAR4_SIGNATURE[..4], b"Rar!");
14/// ```
15pub const RAR4_SIGNATURE: [u8; 7] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00];
16
17/// RAR5 magic signature: `Rar!\x1a\x07\x01\x00`.
18///
19/// ```
20/// use rar_stream::parsing::marker_header::RAR5_SIGNATURE;
21/// assert_eq!(&RAR5_SIGNATURE[..4], b"Rar!");
22/// assert_eq!(RAR5_SIGNATURE[6], 0x01); // distinguishes RAR5 from RAR4
23/// ```
24pub const RAR5_SIGNATURE: [u8; 8] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00];
25
26/// RAR archive version.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum RarVersion {
29 /// RAR 4.x format (also known as RAR 2.9)
30 #[default]
31 Rar4,
32 /// RAR 5.x format
33 Rar5,
34}
35
36impl RarVersion {
37 /// Returns the signature size for this version.
38 pub fn signature_size(&self) -> usize {
39 match self {
40 Self::Rar4 => 7,
41 Self::Rar5 => 8,
42 }
43 }
44}
45
46/// Parsed RAR marker header (the first header in every RAR archive).
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct MarkerHeader {
49 /// Header CRC (RAR4 only; always 0x6152 for valid archives).
50 pub crc: u16,
51 /// Header type byte.
52 pub header_type: u8,
53 /// Header flags.
54 pub flags: u16,
55 /// Header size in bytes.
56 pub size: u32,
57 /// Detected RAR format version.
58 pub version: RarVersion,
59}
60
61/// Parser for the RAR archive marker (signature) header.
62pub struct MarkerHeaderParser;
63
64impl MarkerHeaderParser {
65 /// Size of the RAR4 marker header in bytes.
66 pub const HEADER_SIZE: usize = 7;
67
68 /// Detect RAR version from buffer without full parsing.
69 ///
70 /// Returns [`RarVersion::Rar4`] or [`RarVersion::Rar5`] based on the
71 /// signature bytes, or [`RarError::InvalidSignature`] if the buffer
72 /// doesn't start with a valid RAR signature.
73 ///
74 /// ```
75 /// use rar_stream::parsing::marker_header::MarkerHeaderParser;
76 /// use rar_stream::parsing::marker_header::RarVersion;
77 ///
78 /// let rar4 = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00];
79 /// assert_eq!(MarkerHeaderParser::detect_version(&rar4).unwrap(), RarVersion::Rar4);
80 ///
81 /// let rar5 = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00];
82 /// assert_eq!(MarkerHeaderParser::detect_version(&rar5).unwrap(), RarVersion::Rar5);
83 ///
84 /// assert!(MarkerHeaderParser::detect_version(b"not rar").is_err());
85 /// ```
86 pub fn detect_version(buffer: &[u8]) -> Result<RarVersion> {
87 if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
88 return Ok(RarVersion::Rar5);
89 }
90 if buffer.len() >= 7 && buffer[..7] == RAR4_SIGNATURE {
91 return Ok(RarVersion::Rar4);
92 }
93 Err(RarError::InvalidSignature)
94 }
95
96 /// Parse marker header from buffer.
97 /// The marker header is actually parsed as a generic RAR header.
98 /// The "size" field tells us how many bytes this header consumes.
99 pub fn parse(buffer: &[u8]) -> Result<MarkerHeader> {
100 if buffer.len() < Self::HEADER_SIZE {
101 return Err(RarError::BufferTooSmall {
102 needed: Self::HEADER_SIZE,
103 have: buffer.len(),
104 });
105 }
106
107 // Check for RAR5 first (longer signature)
108 if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
109 return Ok(MarkerHeader {
110 crc: 0,
111 header_type: 0,
112 flags: 0,
113 size: 8,
114 version: RarVersion::Rar5,
115 });
116 }
117
118 // Verify RAR4 signature (first 7 bytes)
119 if buffer[..7] != RAR4_SIGNATURE {
120 return Err(RarError::InvalidSignature);
121 }
122
123 // Parse as generic header structure (RAR4)
124 let crc = u16::from_le_bytes([buffer[0], buffer[1]]);
125 let header_type = buffer[2];
126 let flags = u16::from_le_bytes([buffer[3], buffer[4]]);
127 let size = u16::from_le_bytes([buffer[5], buffer[6]]) as u32;
128
129 Ok(MarkerHeader {
130 crc,
131 header_type,
132 flags,
133 size,
134 version: RarVersion::Rar4,
135 })
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_parse_rar4_marker() {
145 // RAR4 signature + header
146 let buffer = [
147 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00, // RAR4 signature
148 0x00, 0x00, 0x00, 0x00, // padding for HEADER_SIZE
149 ];
150 let header = MarkerHeaderParser::parse(&buffer).unwrap();
151 assert_eq!(header.header_type, b'r'); // 0x72
152 }
153
154 #[test]
155 fn test_invalid_signature() {
156 let buffer = [0x00; 11];
157 assert!(matches!(
158 MarkerHeaderParser::parse(&buffer),
159 Err(RarError::InvalidSignature)
160 ));
161 }
162
163 #[test]
164 fn test_buffer_too_small() {
165 let buffer = [0x52, 0x61, 0x72];
166 assert!(matches!(
167 MarkerHeaderParser::parse(&buffer),
168 Err(RarError::BufferTooSmall { .. })
169 ));
170 }
171}