use crate::error::NsfError;
use crate::time::Timedate;
pub const NOTE_SIGNATURE: [u8; 2] = [0x04, 0x00];
pub const NOTE_HEADER_BYTES: usize = 100;
#[allow(missing_docs)]
pub mod class {
pub const DOCUMENT: u16 = 0x0001;
pub const INFO: u16 = 0x0002;
pub const FORM: u16 = 0x0004;
pub const VIEW: u16 = 0x0008;
pub const ICON: u16 = 0x0010;
pub const DESIGN: u16 = 0x0020;
pub const ACL: u16 = 0x0040;
pub const HELP_INDEX: u16 = 0x0080;
pub const HELP: u16 = 0x0100;
pub const FILTER: u16 = 0x0200;
pub const FIELD: u16 = 0x0400;
pub const REPLFORMULA: u16 = 0x0800;
pub const PRIVATE: u16 = 0x1000;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoteHeader {
pub size: u32,
pub rrv_identifier: u32,
pub file_identifier: Timedate,
pub note_identifier: Timedate,
pub sequence_number: u32,
pub sequence_time: Timedate,
pub status_flags: u16,
pub note_class: u16,
pub modification_time: Timedate,
pub number_of_note_items: u16,
pub number_of_responses: u16,
pub non_summary_data_identifier: u32,
pub non_summary_data_size: u32,
pub access_time: Timedate,
pub creation_time: Timedate,
pub parent_note_identifier: u32,
pub folder_reference_count: u32,
pub folder_note_identifier: u32,
}
impl NoteHeader {
pub fn parse(bytes: &[u8]) -> Result<Self, NsfError> {
if bytes.len() < NOTE_HEADER_BYTES {
return Err(NsfError::TooShort {
actual: bytes.len(),
required: NOTE_HEADER_BYTES,
});
}
if bytes[0] != NOTE_SIGNATURE[0] || bytes[1] != NOTE_SIGNATURE[1] {
return Err(NsfError::BadFileSignature {
observed: [bytes[0], bytes[1]],
});
}
let u16_at = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]);
let u32_at = |o: usize| {
u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]])
};
Ok(Self {
size: u32_at(2),
rrv_identifier: u32_at(6),
file_identifier: Timedate::from_bytes(&bytes[10..18])?,
note_identifier: Timedate::from_bytes(&bytes[18..26])?,
sequence_number: u32_at(26),
sequence_time: Timedate::from_bytes(&bytes[30..38])?,
status_flags: u16_at(38),
note_class: u16_at(40),
modification_time: Timedate::from_bytes(&bytes[42..50])?,
number_of_note_items: u16_at(50),
number_of_responses: u16_at(54),
non_summary_data_identifier: u32_at(56),
non_summary_data_size: u32_at(60),
access_time: Timedate::from_bytes(&bytes[64..72])?,
creation_time: Timedate::from_bytes(&bytes[72..80])?,
parent_note_identifier: u32_at(80),
folder_reference_count: u32_at(86),
folder_note_identifier: u32_at(94),
})
}
pub fn is_document(&self) -> bool {
self.note_class & class::DOCUMENT != 0
}
pub fn is_design(&self) -> bool {
const DESIGN_MASK: u16 = class::FORM
| class::VIEW
| class::ICON
| class::DESIGN
| class::HELP
| class::HELP_INDEX
| class::FILTER
| class::FIELD
| class::REPLFORMULA
| class::PRIVATE;
self.note_class & DESIGN_MASK != 0
}
pub fn unid_hex(&self) -> String {
format!(
"{}{}",
self.file_identifier.as_hex_id(),
self.note_identifier.as_hex_id()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synthetic_note(note_class: u16, item_count: u16) -> Vec<u8> {
let mut buf = vec![0u8; NOTE_HEADER_BYTES + 32];
buf[0..2].copy_from_slice(&NOTE_SIGNATURE);
buf[2..6].copy_from_slice(&512u32.to_le_bytes());
buf[6..10].copy_from_slice(&12345u32.to_le_bytes());
buf[40..42].copy_from_slice(¬e_class.to_le_bytes());
buf[50..52].copy_from_slice(&item_count.to_le_bytes());
buf
}
#[test]
fn parses_document_note() {
let buf = synthetic_note(class::DOCUMENT, 17);
let n = NoteHeader::parse(&buf).unwrap();
assert!(n.is_document());
assert!(!n.is_design());
assert_eq!(n.number_of_note_items, 17);
assert_eq!(n.rrv_identifier, 12345);
assert_eq!(n.size, 512);
}
#[test]
fn parses_form_note_as_design() {
let buf = synthetic_note(class::FORM, 8);
let n = NoteHeader::parse(&buf).unwrap();
assert!(!n.is_document());
assert!(n.is_design());
}
#[test]
fn parses_acl_note_neither_document_nor_design() {
let buf = synthetic_note(class::ACL, 3);
let n = NoteHeader::parse(&buf).unwrap();
assert!(!n.is_document());
assert!(!n.is_design());
}
#[test]
fn rejects_bad_signature() {
let mut buf = synthetic_note(class::DOCUMENT, 1);
buf[0] = 0xFF;
assert!(NoteHeader::parse(&buf).is_err());
}
#[test]
fn unid_hex_is_32_chars() {
let buf = synthetic_note(class::DOCUMENT, 1);
let n = NoteHeader::parse(&buf).unwrap();
assert_eq!(n.unid_hex().len(), 32);
}
}