1use std::io::{Cursor, Read, Seek, SeekFrom, Write};
2
3use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use fwob_core::{Field, FieldSemantic, FieldType, Schema};
5
6use crate::{Result, V2Error};
7
8pub const MAGIC: &[u8; 4] = b"FWB2";
9pub const VERSION: u8 = 3;
10pub const FILE_HEADER_LEN: u64 = 4096;
11pub const MIN_PAGE_SIZE: u32 = 1024;
12pub const MAX_PAGE_SIZE: u32 = 16 * 1024 * 1024;
13const MAX_HEADER_FIELDS: u16 = 768;
14const MAX_HEADER_STRINGS: u32 = 2048;
15const TITLE_LENGTH_OFFSET: u64 = 4 + 1 + 4 + 8 + 8 + 2;
16const TITLE_BYTES_OFFSET: u64 = TITLE_LENGTH_OFFSET + 2;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct FileHeader {
20 pub page_size: u32,
21 pub page_count: u64,
22 pub frame_count: u64,
23 pub key_field_index: u16,
24 pub title: String,
25 pub schema: Schema,
26 pub string_table: Vec<String>,
27}
28
29impl FileHeader {
30 pub fn page_offset(&self, page_index: u64) -> u64 {
31 FILE_HEADER_LEN + page_index * u64::from(self.page_size)
32 }
33}
34
35pub fn read_file_header<R: Read + Seek>(reader: &mut R) -> Result<FileHeader> {
36 reader.seek(SeekFrom::Start(0))?;
37 let mut header_bytes = vec![0; FILE_HEADER_LEN as usize];
38 reader.read_exact(&mut header_bytes)?;
39 let reader = &mut Cursor::new(header_bytes);
40 let mut magic = [0u8; 4];
41 reader.read_exact(&mut magic)?;
42 if &magic != MAGIC {
43 return Err(V2Error::InvalidFileHeader);
44 }
45 let version = reader.read_u8()?;
46 if version != VERSION {
47 return Err(V2Error::InvalidFileHeader);
48 }
49 let page_size = reader.read_u32::<LittleEndian>()?;
50 if !(MIN_PAGE_SIZE..=MAX_PAGE_SIZE).contains(&page_size) {
51 return Err(V2Error::InvalidFileHeader);
52 }
53 let page_count = reader.read_u64::<LittleEndian>()?;
54 let frame_count = reader.read_u64::<LittleEndian>()?;
55 let key_field_index = reader.read_u16::<LittleEndian>()?;
56 let title = read_len_string(reader)?;
57 let frame_type = read_len_string(reader)?;
58 let field_count = reader.read_u16::<LittleEndian>()?;
59 if field_count == 0 || field_count > MAX_HEADER_FIELDS {
60 return Err(V2Error::InvalidFileHeader);
61 }
62 let mut fields = Vec::with_capacity(field_count as usize);
63 let mut offset = 0u32;
64 for _ in 0..field_count {
65 let name = read_len_string(reader)?;
66 let field_type = FieldType::from_v1_id(reader.read_u8()?)?;
67 let length = reader.read_u16::<LittleEndian>()?;
68 let semantic = FieldSemantic::from_id(reader.read_u8()?)?;
69 fields.push(Field::new(name, field_type, length, offset).with_semantic(semantic));
70 offset += u32::from(length);
71 }
72 let string_count = reader.read_u32::<LittleEndian>()?;
73 if string_count > MAX_HEADER_STRINGS {
74 return Err(V2Error::InvalidFileHeader);
75 }
76 let mut string_table = Vec::with_capacity(string_count as usize);
77 for _ in 0..string_count {
78 string_table.push(read_len_string(reader)?);
79 }
80 let schema = Schema::new(frame_type, fields, key_field_index as usize)?;
81 Ok(FileHeader {
82 page_size,
83 page_count,
84 frame_count,
85 key_field_index,
86 title,
87 schema,
88 string_table,
89 })
90}
91
92pub fn write_file_header<W: Write + Seek>(writer: &mut W, header: &FileHeader) -> Result<()> {
93 if header.schema.fields.len() > MAX_HEADER_FIELDS as usize
94 || header.string_table.len() > MAX_HEADER_STRINGS as usize
95 {
96 return Err(V2Error::InvalidFileHeader);
97 }
98 writer.seek(SeekFrom::Start(0))?;
99 let mut bytes = Vec::new();
100 bytes.extend_from_slice(MAGIC);
101 bytes.push(VERSION);
102 bytes.extend_from_slice(&header.page_size.to_le_bytes());
103 bytes.extend_from_slice(&header.page_count.to_le_bytes());
104 bytes.extend_from_slice(&header.frame_count.to_le_bytes());
105 bytes.extend_from_slice(&header.key_field_index.to_le_bytes());
106 write_len_string(&mut bytes, &header.title)?;
107 write_len_string(&mut bytes, &header.schema.frame_type)?;
108 bytes.extend_from_slice(&(header.schema.fields.len() as u16).to_le_bytes());
109 for field in &header.schema.fields {
110 write_len_string(&mut bytes, &field.name)?;
111 bytes.push(field.field_type as u8);
112 bytes.extend_from_slice(&field.length.to_le_bytes());
113 bytes.push(field.semantic.id());
114 }
115 bytes.extend_from_slice(&(header.string_table.len() as u32).to_le_bytes());
116 for value in &header.string_table {
117 write_len_string(&mut bytes, value)?;
118 }
119 if bytes.len() > FILE_HEADER_LEN as usize {
120 return Err(V2Error::InvalidFileHeader);
121 }
122 writer.write_all(&bytes)?;
123 writer.write_all(&vec![0u8; FILE_HEADER_LEN as usize - bytes.len()])?;
124 Ok(())
125}
126
127pub fn update_metadata(
128 path: impl AsRef<std::path::Path>,
129 title: Option<&str>,
130 string_table: Option<&[String]>,
131) -> Result<()> {
132 let mut file = std::fs::OpenOptions::new()
133 .read(true)
134 .write(true)
135 .open(path)?;
136 let mut header = read_file_header(&mut file)?;
137 let actual_len = file.metadata()?.len();
138 let expected_len = FILE_HEADER_LEN + header.page_count * u64::from(header.page_size);
139 if actual_len != expected_len {
140 return Err(V2Error::InvalidFileHeader);
141 }
142 if let Some(title) = title {
143 if title.is_empty() {
144 return Err(V2Error::InvalidFileHeader);
145 }
146 if string_table.is_none() && title.len() == header.title.len() {
147 file.seek(SeekFrom::Start(TITLE_BYTES_OFFSET))?;
148 file.write_all(title.as_bytes())?;
149 file.flush()?;
150 return Ok(());
151 }
152 header.title = title.to_owned();
153 }
154 if let Some(strings) = string_table {
155 header.string_table = strings.to_vec();
156 }
157 write_file_header(&mut file, &header)?;
158 file.flush()?;
159 Ok(())
160}
161
162pub fn update_counts<W: Write + Seek>(
163 writer: &mut W,
164 page_count: u64,
165 frame_count: u64,
166) -> Result<()> {
167 writer.seek(SeekFrom::Start(9))?;
168 writer.write_u64::<LittleEndian>(page_count)?;
169 writer.write_u64::<LittleEndian>(frame_count)?;
170 Ok(())
171}
172
173fn read_len_string<R: Read>(reader: &mut R) -> Result<String> {
174 let len = reader.read_u16::<LittleEndian>()?;
175 let mut bytes = vec![0u8; len as usize];
176 reader.read_exact(&mut bytes)?;
177 String::from_utf8(bytes).map_err(|_| V2Error::InvalidFileHeader)
178}
179
180fn write_len_string<W: Write>(writer: &mut W, value: &str) -> Result<()> {
181 if value.len() > u16::MAX as usize {
182 return Err(V2Error::InvalidFileHeader);
183 }
184 writer.write_u16::<LittleEndian>(value.len() as u16)?;
185 writer.write_all(value.as_bytes())?;
186 Ok(())
187}