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}