Skip to main content

fwob_v2/
file_header.rs

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}