1#[allow(clippy::module_name_repetitions)]
2use super::version::Version;
3use anyhow::{Result, anyhow};
4use core::str;
5use memmap3::Mmap;
7use serde::{Serialize, Serializer, ser::SerializeMap};
8use std::ops::RangeInclusive;
9
10#[derive(Clone, Debug, Hash)]
18pub struct Header {
19 pub version: Version,
20 pub text_offset: RangeInclusive<usize>,
21 pub data_offset: RangeInclusive<usize>,
22 pub analysis_offset: RangeInclusive<usize>,
23}
24impl Serialize for Header {
25 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26 where
27 S: Serializer,
28 {
29 let mut state = serializer.serialize_map(Some(2))?;
30 state.serialize_entry("version", &self.version)?;
31 state.serialize_entry("text_offset", &self.text_offset)?;
32 state.serialize_entry("data_offset", &self.data_offset)?;
33 state.serialize_entry("analysis_offset", &self.analysis_offset)?;
34 state.end()
35 }
36}
37
38impl Header {
39 #[must_use]
40 pub const fn new() -> Self {
41 Self {
42 version: Version::V3_1,
43 text_offset: 0..=0,
44 data_offset: 0..=0,
45 analysis_offset: 0..=0,
46 }
47 }
48 pub fn from_mmap(mmap: &Mmap) -> Result<Self> {
55 Self::check_header_spaces(&mmap[6..=9])?;
57 Ok(Self {
61 version: Self::get_version(mmap)?,
62 text_offset: Self::get_text_offsets(mmap)?,
63 data_offset: Self::get_data_offsets(mmap)?,
64 analysis_offset: Self::get_analysis_offsets(mmap)?,
65 })
66 }
67
68 pub fn get_version(mmap: &Mmap) -> Result<Version> {
72 let version = String::from_utf8(mmap[..6].to_vec())?;
73 Self::check_fcs_version(&version)
74 }
75
76 pub fn check_fcs_version(version: &str) -> Result<Version> {
80 match version {
81 "FCS1.0" => Ok(Version::V1_0),
82 "FCS2.0" => Ok(Version::V2_0),
83 "FCS3.0" => Ok(Version::V3_0),
84 "FCS3.1" => Ok(Version::V3_1),
85 "FCS3.2" => Ok(Version::V3_2),
86 "FCS4.0" => Ok(Version::V4_0),
87 _ => Err(anyhow!("Invalid FCS version: {}", version)),
88 }
89 }
90 pub fn check_header_spaces(buffer: &[u8]) -> Result<()> {
94 if bytecount::count(buffer, b' ') != 4 {
95 return Err(anyhow!(
96 "Invalid number of spaces in header segment. File may be corrupted."
97 ));
98 }
99 Ok(())
100 }
101 fn get_offset_from_header(mmap: &Mmap, start: usize, end: usize) -> Result<usize> {
103 let offset_str = std::str::from_utf8(&mmap[start..=end])
104 .map_err(|_| anyhow!("Invalid UTF-8 in header segment"))?;
105 Ok(offset_str.trim().parse::<usize>()?)
106 }
107 fn get_text_offset_start(mmap: &Mmap) -> Result<usize> {
109 Self::get_offset_from_header(mmap, 10, 17)
110 }
111 fn get_text_offset_end(mmap: &Mmap) -> Result<usize> {
113 Self::get_offset_from_header(mmap, 18, 25)
114 }
115 fn get_data_offset_start(mmap: &Mmap) -> Result<usize> {
117 Self::get_offset_from_header(mmap, 26, 33)
118 }
119 fn get_data_offset_end(mmap: &Mmap) -> Result<usize> {
121 Self::get_offset_from_header(mmap, 34, 41)
122 }
123 fn get_analysis_offset_start(mmap: &Mmap) -> Result<usize> {
125 Self::get_offset_from_header(mmap, 42, 49)
126 }
127 fn get_analysis_offset_end(mmap: &Mmap) -> Result<usize> {
129 Self::get_offset_from_header(mmap, 50, 57)
130 }
131 fn get_text_offsets(mmap: &Mmap) -> Result<RangeInclusive<usize>> {
133 let text_offset_start = Self::get_text_offset_start(mmap)?;
134 let text_offset_end = Self::get_text_offset_end(mmap)?;
135 Ok(text_offset_start..=text_offset_end)
136 }
137 fn get_data_offsets(mmap: &Mmap) -> Result<RangeInclusive<usize>> {
139 let data_offset_start = Self::get_data_offset_start(mmap)?;
140 let data_offset_end = Self::get_data_offset_end(mmap)?;
141 Ok(data_offset_start..=data_offset_end)
142 }
143 fn get_analysis_offsets(mmap: &Mmap) -> Result<RangeInclusive<usize>> {
145 let analysis_offset_start = Self::get_analysis_offset_start(mmap)?;
146 let analysis_offset_end = Self::get_analysis_offset_end(mmap)?;
147 Ok(analysis_offset_start..=analysis_offset_end)
148 }
149 pub fn check_fcs_offsets(mmap: &Mmap) -> Result<()> {
161 println!(
162 "HEADER (first 58 bytes): {:?}",
163 std::str::from_utf8(&mmap[0..58]).unwrap_or("<invalid utf-8>")
164 );
165 println!(
166 "TEXT segment start offset: {:?}",
167 Self::get_text_offset_start(mmap)?
168 );
169 println!(
170 "TEXT segment end offset: {:?}",
171 Self::get_text_offset_end(mmap)?
172 );
173 println!(
174 "DATA segment start offset: {:?}",
175 Self::get_data_offset_start(mmap)?
176 );
177 println!(
178 "DATA segment end offset: {:?}",
179 Self::get_data_offset_end(mmap)?
180 );
181 println!(
182 "ANALYSIS segment start offset (optional): {:?}",
183 Self::get_analysis_offset_start(mmap)
184 );
185 println!(
186 "ANALYSIS segment end offset (optional): {:?}",
187 Self::get_analysis_offset_end(mmap)
188 );
189 println!(
191 "header range of TEXT: {:?}",
192 std::str::from_utf8(&mmap[4700..=5216]).unwrap_or("<invalid utf-8>")
193 );
194 Ok(())
195 }
196}
197impl Default for Header {
198 fn default() -> Self {
199 Self::new()
200 }
201}