1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! The common file header used by both the PRC and PDB databases

use core::{
	fmt::{self, Debug, Display},
	str,
};
use std::io::{self, Cursor, Read, Write};

use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

use crate::time::PalmTimestamp;

/// Length, in bytes, of the [`DatabaseHeader`]
pub const DATABASE_HEADER_LENGTH: usize = 78;

/// The common file header used by both the PRC and PDB databases.
///
/// This is located at the start of the database (offset `0`).
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct DatabaseHeader {
	pub name: [u8; 32],
	pub attributes: u16,
	pub version: u16,
	pub creation_time: PalmTimestamp,
	pub modification_time: PalmTimestamp,
	pub backup_time: PalmTimestamp,
	pub modification_number: u32,
	pub app_info_id: u32,
	pub sort_info_id: u32,
	pub type_code: [u8; 4],
	pub creator_code: [u8; 4],
	pub unique_id_seed: u32,
	pub next_record_list: u32,
	pub record_count: u16,
}

impl DatabaseHeader {
	pub const SIZE: usize = DATABASE_HEADER_LENGTH;

	/// Read the database header from the given byte slice.
	pub fn from_bytes(rdr: &mut Cursor<&[u8]>) -> Result<Self, io::Error> {
		// Read all the data

		let name = {
			let mut buf = [0u8; 32];
			rdr.read_exact(&mut buf)?;
			buf
		};

		let attributes = rdr.read_u16::<BigEndian>()?;
		let version = rdr.read_u16::<BigEndian>()?;
		let creation_time = rdr.read_u32::<BigEndian>()?;
		let modification_time = rdr.read_u32::<BigEndian>()?;
		let backup_time = rdr.read_u32::<BigEndian>()?;
		let modification_number = rdr.read_u32::<BigEndian>()?;
		let app_info_id = rdr.read_u32::<BigEndian>()?;
		let sort_info_id = rdr.read_u32::<BigEndian>()?;

		let type_code = {
			let mut buf = [0u8; 4];
			rdr.read_exact(&mut buf)?;
			buf
		};

		let creator_code = {
			let mut buf = [0u8; 4];
			rdr.read_exact(&mut buf)?;
			buf
		};

		let unique_id_seed = rdr.read_u32::<BigEndian>()?;
		let next_record_list = rdr.read_u32::<BigEndian>()?;
		let record_count = rdr.read_u16::<BigEndian>()?;

		// Populate structure

		let created_header = Self {
			name,
			attributes,
			version,
			creation_time: PalmTimestamp(creation_time),
			modification_time: PalmTimestamp(modification_time),
			backup_time: PalmTimestamp(backup_time),
			modification_number,
			app_info_id,
			sort_info_id,
			type_code,
			creator_code,
			unique_id_seed,
			next_record_list,
			record_count,
		};

		Ok(created_header)
	}

	pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
		let mut cursor = Cursor::new(vec![0_u8; Self::SIZE]);

		let DatabaseHeader {
			name,
			attributes,
			version,
			creation_time,
			modification_time,
			backup_time,
			modification_number,
			app_info_id,
			sort_info_id,
			type_code,
			creator_code,
			unique_id_seed,
			next_record_list,
			record_count,
		} = self;

		cursor.write(name)?;
		cursor.write_u16::<BigEndian>(*attributes)?;
		cursor.write_u16::<BigEndian>(*version)?;
		cursor.write_u32::<BigEndian>(creation_time.0)?;
		cursor.write_u32::<BigEndian>(modification_time.0)?;
		cursor.write_u32::<BigEndian>(backup_time.0)?;
		cursor.write_u32::<BigEndian>(*modification_number)?;
		cursor.write_u32::<BigEndian>(*app_info_id)?;
		cursor.write_u32::<BigEndian>(*sort_info_id)?;
		cursor.write(type_code)?;
		cursor.write(creator_code)?;
		cursor.write_u32::<BigEndian>(*unique_id_seed)?;
		cursor.write_u32::<BigEndian>(*next_record_list)?;
		cursor.write_u16::<BigEndian>(*record_count)?;

		Ok(cursor.into_inner())
	}

	/// Return the friendly name of the database as a byte slice, containing only the data bytes
	/// of the name (that is, the null terminating bytes are trimmed).
	pub fn name_trimmed<'x>(&'x self) -> &'x [u8] {
		let mut idx = 0;
		while idx < self.name.len() {
			if self.name[idx] == 0u8 {
				break;
			}

			idx += 1;
		}

		&self.name[..idx]
	}

	/// Attempt to convert the friendly name of the database to a [`str`][core::str]
	pub fn name_try_str<'x>(&'x self) -> Result<&'x str, str::Utf8Error> {
		str::from_utf8(self.name_trimmed())
	}

	/// Attempt to convert the database type code to a [`str`][core::str]
	pub fn type_code_try_str<'x>(&'x self) -> Result<&'x str, str::Utf8Error> {
		let mut idx = 0;
		while idx < self.type_code.len() {
			if self.type_code[idx] == 0u8 {
				break;
			}

			idx += 1;
		}

		str::from_utf8(&self.type_code[0..idx])
	}

	/// Attempt to convert the database creator code to a [`str`][core::str]
	pub fn creator_code_try_str<'x>(&'x self) -> Result<&'x str, str::Utf8Error> {
		let mut idx = 0;
		while idx < self.creator_code.len() {
			if self.creator_code[idx] == 0u8 {
				break;
			}

			idx += 1;
		}

		str::from_utf8(&self.creator_code[0..idx])
	}
}

impl Display for DatabaseHeader {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(
			f,
			"DatabaseHeader({:?}, created={})",
			self.name_try_str().unwrap_or(""),
			self.creation_time,
		)
	}
}