Skip to main content

fwob_v1/
header.rs

1use std::io::{Read, Seek, SeekFrom, Write};
2
3use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use fwob_core::{Field, FieldType, Schema};
5
6use crate::{Result, V1Error};
7
8pub const SIGNATURE: &[u8; 4] = b"FWOB";
9pub const VERSION: u8 = 1;
10pub const HEADER_LEN: u64 = 214;
11pub const DEFAULT_PREFIX_LEN: u32 = 2048;
12pub const DEFAULT_STRING_TABLE_PRESERVED_LEN: u32 = DEFAULT_PREFIX_LEN - HEADER_LEN as u32;
13pub const MAX_FIELDS: usize = 16;
14pub const MAX_FIELD_NAME_LEN: usize = 8;
15pub const MAX_FRAME_TYPE_LEN: usize = 16;
16pub const MAX_TITLE_LEN: usize = 16;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Header {
20    pub version: u8,
21    pub field_count: u8,
22    pub field_lengths: Vec<u8>,
23    pub field_types: u64,
24    pub field_names: Vec<String>,
25    pub string_count: u32,
26    pub string_table_length: u32,
27    pub string_table_preserved_length: u32,
28    pub frame_count: u64,
29    pub frame_length: u32,
30    pub frame_type: String,
31    pub title: String,
32}
33
34impl Header {
35    pub fn first_frame_position(&self) -> u64 {
36        HEADER_LEN + u64::from(self.string_table_preserved_length)
37    }
38
39    pub fn string_table_position(&self) -> u64 {
40        HEADER_LEN
41    }
42
43    pub fn string_table_ending(&self) -> u64 {
44        HEADER_LEN + u64::from(self.string_table_length)
45    }
46
47    pub fn file_length(&self) -> u64 {
48        self.first_frame_position() + u64::from(self.frame_length) * self.frame_count
49    }
50
51    pub fn schema(&self, key_field_index: usize) -> Result<Schema> {
52        if key_field_index >= self.field_count as usize {
53            return Err(V1Error::KeyFieldIndexOutOfRange(key_field_index));
54        }
55
56        let mut offset = 0u32;
57        let mut fields = Vec::with_capacity(self.field_count as usize);
58        for i in 0..self.field_count as usize {
59            let field_type_id = ((self.field_types >> (i * 4)) & 0x0f) as u8;
60            let field_type = FieldType::from_v1_id(field_type_id)?;
61            let length = u16::from(self.field_lengths[i]);
62            fields.push(Field::new(
63                self.field_names[i].clone(),
64                field_type,
65                length,
66                offset,
67            ));
68            offset += u32::from(length);
69        }
70        Schema::new(self.frame_type.clone(), fields, key_field_index).map_err(Into::into)
71    }
72}
73
74pub fn read_header<R: Read>(reader: &mut R) -> Result<Header> {
75    let mut sig = [0u8; 4];
76    reader.read_exact(&mut sig)?;
77    if &sig != SIGNATURE {
78        return Err(V1Error::CorruptedHeader);
79    }
80
81    let version = reader.read_u8()?;
82    if version != VERSION {
83        return Err(V1Error::CorruptedHeader);
84    }
85
86    let field_count = reader.read_u8()?;
87    if field_count as usize > MAX_FIELDS {
88        return Err(V1Error::CorruptedHeader);
89    }
90
91    let mut all_lengths = [0u8; MAX_FIELDS];
92    reader.read_exact(&mut all_lengths)?;
93    let field_lengths = all_lengths[..field_count as usize].to_vec();
94    let field_types = reader.read_u64::<LittleEndian>()?;
95
96    let mut all_names = Vec::with_capacity(MAX_FIELDS);
97    for _ in 0..MAX_FIELDS {
98        let mut raw = [0u8; MAX_FIELD_NAME_LEN];
99        reader.read_exact(&mut raw)?;
100        all_names.push(trim_ascii_field(&raw));
101    }
102    let field_names = all_names[..field_count as usize].to_vec();
103    if field_names.iter().any(|name| name.is_empty()) {
104        return Err(V1Error::CorruptedHeader);
105    }
106
107    let string_count = reader.read_i32::<LittleEndian>()?;
108    let string_table_length = reader.read_i32::<LittleEndian>()?;
109    let string_table_preserved_length = reader.read_i32::<LittleEndian>()?;
110    if string_count < 0
111        || string_table_length < 0
112        || string_table_preserved_length < string_table_length
113    {
114        return Err(V1Error::CorruptedHeader);
115    }
116
117    let frame_count = reader.read_i64::<LittleEndian>()?;
118    let frame_length = reader.read_i32::<LittleEndian>()?;
119    if frame_count < 0 || frame_length < 0 {
120        return Err(V1Error::CorruptedHeader);
121    }
122    let expected_frame_length: u32 = field_lengths.iter().map(|&v| u32::from(v)).sum();
123    if frame_length as u32 != expected_frame_length {
124        return Err(V1Error::CorruptedHeader);
125    }
126
127    let mut frame_type = [0u8; MAX_FRAME_TYPE_LEN];
128    reader.read_exact(&mut frame_type)?;
129    let frame_type = trim_ascii_field(&frame_type);
130    if frame_type.is_empty() {
131        return Err(V1Error::CorruptedHeader);
132    }
133
134    let mut title = [0u8; MAX_TITLE_LEN];
135    reader.read_exact(&mut title)?;
136    let title = trim_ascii_field(&title);
137    if title.is_empty() {
138        return Err(V1Error::CorruptedHeader);
139    }
140
141    Ok(Header {
142        version,
143        field_count,
144        field_lengths,
145        field_types,
146        field_names,
147        string_count: string_count as u32,
148        string_table_length: string_table_length as u32,
149        string_table_preserved_length: string_table_preserved_length as u32,
150        frame_count: frame_count as u64,
151        frame_length: frame_length as u32,
152        frame_type,
153        title,
154    })
155}
156
157pub fn write_header<W: Write>(writer: &mut W, header: &Header) -> Result<()> {
158    writer.write_all(SIGNATURE)?;
159    writer.write_u8(header.version)?;
160    writer.write_u8(header.field_count)?;
161
162    writer.write_all(&header.field_lengths)?;
163    if header.field_lengths.len() < MAX_FIELDS {
164        writer.write_all(&vec![0; MAX_FIELDS - header.field_lengths.len()])?;
165    }
166
167    writer.write_u64::<LittleEndian>(header.field_types)?;
168
169    for name in &header.field_names {
170        write_fixed_ascii(writer, name, MAX_FIELD_NAME_LEN)?;
171    }
172    for _ in header.field_names.len()..MAX_FIELDS {
173        writer.write_all(&[0u8; MAX_FIELD_NAME_LEN])?;
174    }
175
176    writer.write_i32::<LittleEndian>(header.string_count as i32)?;
177    writer.write_i32::<LittleEndian>(header.string_table_length as i32)?;
178    writer.write_i32::<LittleEndian>(header.string_table_preserved_length as i32)?;
179    writer.write_i64::<LittleEndian>(header.frame_count as i64)?;
180    writer.write_i32::<LittleEndian>(header.frame_length as i32)?;
181    write_fixed_ascii(writer, &header.frame_type, MAX_FRAME_TYPE_LEN)?;
182    write_fixed_ascii(writer, &header.title, MAX_TITLE_LEN)?;
183    Ok(())
184}
185
186pub fn update_frame_count<W: Write + Seek>(writer: &mut W, frame_count: u64) -> Result<()> {
187    writer.seek(SeekFrom::Start(170))?;
188    writer.write_i64::<LittleEndian>(frame_count as i64)?;
189    Ok(())
190}
191
192pub fn update_string_table_len<W: Write + Seek>(
193    writer: &mut W,
194    string_count: u32,
195    string_table_length: u32,
196) -> Result<()> {
197    writer.seek(SeekFrom::Start(158))?;
198    writer.write_i32::<LittleEndian>(string_count as i32)?;
199    writer.write_i32::<LittleEndian>(string_table_length as i32)?;
200    Ok(())
201}
202
203fn trim_ascii_field(bytes: &[u8]) -> String {
204    let end = bytes
205        .iter()
206        .rposition(|&b| b != 0 && b != b' ')
207        .map(|idx| idx + 1)
208        .unwrap_or(0);
209    String::from_utf8_lossy(&bytes[..end]).into_owned()
210}
211
212fn write_fixed_ascii<W: Write>(writer: &mut W, value: &str, len: usize) -> Result<()> {
213    let bytes = value.as_bytes();
214    if bytes.len() > len {
215        return Err(V1Error::CorruptedHeader);
216    }
217    writer.write_all(bytes)?;
218    if bytes.len() < len {
219        writer.write_all(&vec![b' '; len - bytes.len()])?;
220    }
221    Ok(())
222}