Skip to main content

outlook_pst/ndb/
header.rs

1//! [HEADER](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/c9876f5a-664b-46a3-9887-ba63f113abf5)
2
3use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
5
6use super::{block_id::*, read_write::*, root::*, *};
7use crate::{crc::compute_crc, AnsiPstFile, PstFile, UnicodePstFile};
8
9/// `dwMagic`
10///
11/// ### See also
12/// [Header]
13const HEADER_MAGIC: u32 = u32::from_be_bytes(*b"NDB!");
14
15const HEADER_MAGIC_CLIENT: u16 = u16::from_be_bytes(*b"MS");
16
17/// `wVer`
18///
19/// ### See also
20/// [Header]
21#[repr(u16)]
22#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
23pub enum NdbVersion {
24    Ansi = 15,
25    #[default]
26    Unicode = 23,
27}
28
29impl TryFrom<u16> for NdbVersion {
30    type Error = NdbError;
31
32    fn try_from(value: u16) -> Result<Self, Self::Error> {
33        match value {
34            14..=15 => Ok(NdbVersion::Ansi),
35            23 => Ok(NdbVersion::Unicode),
36            _ => Err(NdbError::InvalidNdbVersion(value)),
37        }
38    }
39}
40
41const NDB_CLIENT_VERSION: u16 = 19;
42const NDB_PLATFORM_CREATE: u8 = 0x01;
43const NDB_PLATFORM_ACCESS: u8 = 0x01;
44const NDB_DEFAULT_NIDS: [u32; 32] = [
45    0x400, 0x400, 0x400, 0x4000, 0x10000, 0x400, 0x400, 0x400, 0x8000, 0x400, 0x400, 0x400, 0x400,
46    0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400,
47    0x400, 0x400, 0x400, 0x400, 0x400, 0x400,
48];
49const NDB_SENTINEL: u8 = 0x80;
50
51/// `bCryptMethod`
52///
53/// ### See also
54/// [Header]
55#[repr(u8)]
56#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
57pub enum NdbCryptMethod {
58    /// `NDB_CRYPT_NONE`: Data blocks are not encoded
59    #[default]
60    None = 0x00,
61    /// `NDB_CRYPT_PERMUTE`: Encoded with the [Permutation algorithm](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/5faf4800-645d-49d1-9457-2ac40eb467bd)
62    Permute = 0x01,
63    /// `NDB_CRYPT_CYCLIC`: Encoded with the [Cyclic algorithm](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/9979fc01-0a3e-496f-900f-a6a867951f23)
64    Cyclic = 0x02,
65}
66
67impl TryFrom<u8> for NdbCryptMethod {
68    type Error = NdbError;
69
70    fn try_from(value: u8) -> Result<Self, Self::Error> {
71        match value {
72            0x00 => Ok(NdbCryptMethod::None),
73            0x01 => Ok(NdbCryptMethod::Permute),
74            0x02 => Ok(NdbCryptMethod::Cyclic),
75            _ => Err(NdbError::InvalidNdbCryptMethod(value)),
76        }
77    }
78}
79
80pub trait Header<Pst>: Clone
81where
82    Pst: PstFile,
83{
84    fn version(&self) -> NdbVersion;
85    fn crypt_method(&self) -> NdbCryptMethod;
86    fn next_block(&self) -> <Pst as PstFile>::BlockId;
87    fn next_page(&self) -> <Pst as PstFile>::PageId;
88    fn unique_value(&self) -> u32;
89    fn root(&self) -> &<Pst as PstFile>::Root;
90    fn root_mut(&mut self) -> &mut <Pst as PstFile>::Root;
91}
92
93#[derive(Clone, Debug)]
94pub struct UnicodeHeader {
95    next_page: UnicodePageId,
96    unique: u32,
97    nids: [u32; 32],
98    root: UnicodeRoot,
99    free_map: [u8; 128],
100    free_page_map: [u8; 128],
101    crypt_method: NdbCryptMethod,
102    next_block: UnicodeBlockId,
103
104    reserved1: u32,
105    reserved2: u32,
106    unused1: u64,
107    unused2: u64,
108    reserved3: [u8; 36],
109}
110
111impl UnicodeHeader {
112    pub fn new(root: UnicodeRoot, crypt_method: NdbCryptMethod) -> Self {
113        Self {
114            next_page: UnicodePageId::from(1),
115            unique: 0,
116            nids: NDB_DEFAULT_NIDS,
117            root,
118            free_map: [0xFF; 128],
119            free_page_map: [0xFF; 128],
120            crypt_method,
121            next_block: UnicodeBlockId::from(4),
122            reserved1: 0,
123            reserved2: 0,
124            unused1: 0,
125            unused2: 0,
126            reserved3: [0; 36],
127        }
128    }
129}
130
131impl Header<UnicodePstFile> for UnicodeHeader {
132    fn version(&self) -> NdbVersion {
133        NdbVersion::Unicode
134    }
135
136    fn crypt_method(&self) -> NdbCryptMethod {
137        self.crypt_method
138    }
139
140    fn next_block(&self) -> <UnicodePstFile as PstFile>::BlockId {
141        self.next_block
142    }
143
144    fn next_page(&self) -> <UnicodePstFile as PstFile>::PageId {
145        self.next_page
146    }
147
148    fn unique_value(&self) -> u32 {
149        self.unique
150    }
151
152    fn root(&self) -> &<UnicodePstFile as PstFile>::Root {
153        &self.root
154    }
155
156    fn root_mut(&mut self) -> &mut <UnicodePstFile as PstFile>::Root {
157        &mut self.root
158    }
159}
160
161impl HeaderReadWrite<UnicodePstFile> for UnicodeHeader {
162    fn read(f: &mut dyn Read) -> io::Result<Self> {
163        // dwMagic
164        let magic = f.read_u32::<LittleEndian>()?;
165        if magic != HEADER_MAGIC {
166            return Err(NdbError::InvalidNdbHeaderMagicValue(magic).into());
167        }
168
169        // dwCRCPartial
170        let crc_partial = f.read_u32::<LittleEndian>()?;
171
172        let mut crc_data = [0_u8; 516];
173        f.read_exact(&mut crc_data[..471])?;
174        if crc_partial != compute_crc(0, &crc_data[..471]) {
175            return Err(NdbError::InvalidNdbHeaderPartialCrc(crc_partial).into());
176        }
177
178        let mut cursor = Cursor::new(crc_data);
179
180        // wMagicClient
181        let magic = cursor.read_u16::<LittleEndian>()?;
182        if magic != HEADER_MAGIC_CLIENT {
183            return Err(NdbError::InvalidNdbHeaderMagicClientValue(magic).into());
184        }
185
186        // wVer
187        let version = NdbVersion::try_from(cursor.read_u16::<LittleEndian>()?)?;
188        if version != NdbVersion::Unicode {
189            return Err(NdbError::AnsiPstVersion(version as u16).into());
190        }
191
192        let mut crc_data = cursor.into_inner();
193        f.read_exact(&mut crc_data[471..])?;
194
195        // dwCRCFull
196        let crc_full = f.read_u32::<LittleEndian>()?;
197        if crc_full != compute_crc(0, &crc_data) {
198            return Err(NdbError::InvalidNdbHeaderFullCrc(crc_full).into());
199        }
200
201        let mut cursor = Cursor::new(crc_data);
202        cursor.seek(SeekFrom::Start(4))?;
203
204        // wVerClient
205        let version = cursor.read_u16::<LittleEndian>()?;
206        if version != NDB_CLIENT_VERSION {
207            return Err(NdbError::InvalidNdbHeaderClientVersion(version).into());
208        }
209
210        // bPlatformCreate
211        let platform_create = cursor.read_u8()?;
212        if platform_create != NDB_PLATFORM_CREATE {
213            return Err(NdbError::InvalidNdbHeaderPlatformCreate(platform_create).into());
214        }
215
216        // bPlatformAccess
217        let platform_access = cursor.read_u8()?;
218        if platform_access != NDB_PLATFORM_ACCESS {
219            return Err(NdbError::InvalidNdbHeaderPlatformAccess(platform_access).into());
220        }
221
222        // dwReserved1
223        let reserved1 = cursor.read_u32::<LittleEndian>()?;
224
225        // dwReserved2
226        let reserved2 = cursor.read_u32::<LittleEndian>()?;
227
228        // bidUnused
229        let unused1 = cursor.read_u64::<LittleEndian>()?;
230
231        // bidNextP
232        let next_page = UnicodePageId::read(&mut cursor)?;
233
234        // dwUnique
235        let unique = cursor.read_u32::<LittleEndian>()?;
236
237        // rgnid
238        let mut nids = [0_u32; 32];
239        for nid in nids.iter_mut() {
240            *nid = cursor.read_u32::<LittleEndian>()?;
241        }
242
243        // qwUnused
244        let unused2 = cursor.read_u64::<LittleEndian>()?;
245
246        // root
247        let root = UnicodeRoot::read(&mut cursor)?;
248
249        // dwAlign
250        let align = cursor.read_u32::<LittleEndian>()?;
251        if align != 0 {
252            return Err(NdbError::InvalidNdbHeaderAlignValue(align).into());
253        }
254
255        // rgbFM
256        let mut free_map = [0; 128];
257        cursor.read_exact(&mut free_map)?;
258
259        // rgbFP
260        let mut free_page_map = [0; 128];
261        cursor.read_exact(&mut free_page_map)?;
262
263        // bSentinel
264        let sentinel = cursor.read_u8()?;
265        if sentinel != NDB_SENTINEL {
266            return Err(NdbError::InvalidNdbHeaderSentinelValue(sentinel).into());
267        }
268
269        // bCryptMethod
270        let crypt_method = NdbCryptMethod::try_from(cursor.read_u8()?)?;
271
272        // rgbReserved
273        let reserved = cursor.read_u16::<LittleEndian>()?;
274        if reserved != 0 {
275            return Err(NdbError::InvalidNdbHeaderReservedValue(reserved).into());
276        }
277
278        // bidNextB
279        let next_block = UnicodeBlockId::read(&mut cursor)?;
280
281        // rgbReserved2, bReserved, rgbReserved3 (total 36 bytes)
282        let mut reserved3 = [0_u8; 36];
283        f.read_exact(&mut reserved3)?;
284
285        Ok(Self {
286            next_page,
287            unique,
288            nids,
289            root,
290            free_map,
291            free_page_map,
292            crypt_method,
293            next_block,
294            reserved1,
295            reserved2,
296            unused1,
297            unused2,
298            reserved3,
299        })
300    }
301
302    fn write(&self, f: &mut dyn Write) -> io::Result<()> {
303        let mut cursor = Cursor::new([0_u8; 516]);
304        // wMagicClient
305        cursor.write_u16::<LittleEndian>(HEADER_MAGIC_CLIENT)?;
306        // wVer
307        cursor.write_u16::<LittleEndian>(NdbVersion::Unicode as u16)?;
308        // wVerClient
309        cursor.write_u16::<LittleEndian>(NDB_CLIENT_VERSION)?;
310        // bPlatformCreate
311        cursor.write_u8(NDB_PLATFORM_CREATE)?;
312        // bPlatformAccess
313        cursor.write_u8(NDB_PLATFORM_ACCESS)?;
314        // dwReserved1
315        cursor.write_u32::<LittleEndian>(self.reserved1)?;
316        // dwReserved2
317        cursor.write_u32::<LittleEndian>(self.reserved2)?;
318        // bidUnused
319        cursor.write_u64::<LittleEndian>(self.unused1)?;
320        // bidNextP
321        self.next_page.write(&mut cursor)?;
322        // dwUnique
323        cursor.write_u32::<LittleEndian>(self.unique)?;
324        // rgnid
325        for nid in self.nids.iter() {
326            cursor.write_u32::<LittleEndian>(*nid)?;
327        }
328        // qwUnused
329        cursor.write_u64::<LittleEndian>(self.unused2)?;
330        // root
331        self.root.write(&mut cursor)?;
332        // dwAlign
333        cursor.write_u32::<LittleEndian>(0)?;
334        // rgbFM
335        cursor.write_all(&self.free_map)?;
336        // rgbFP
337        cursor.write_all(&self.free_page_map)?;
338        // bSentinel
339        cursor.write_u8(NDB_SENTINEL)?;
340        // bCryptMethod
341        cursor.write_u8(self.crypt_method as u8)?;
342        // rgbReserved
343        cursor.write_u16::<LittleEndian>(0)?;
344        // bidNextB
345        self.next_block.write(&mut cursor)?;
346
347        let crc_data = cursor.into_inner();
348        let crc_partial = compute_crc(0, &crc_data[..471]);
349        let crc_full = compute_crc(0, &crc_data);
350
351        // dwMagic
352        f.write_u32::<LittleEndian>(HEADER_MAGIC)?;
353        // dwCRCPartial
354        f.write_u32::<LittleEndian>(crc_partial)?;
355
356        f.write_all(&crc_data)?;
357
358        // dwCRCFull
359        f.write_u32::<LittleEndian>(crc_full)?;
360
361        // rgbReserved2, bReserved, rgbReserved3 (total 36 bytes)
362        f.write_all(&self.reserved3)
363    }
364
365    fn update_unique(&mut self) {
366        self.unique = self.unique.wrapping_add(1);
367    }
368
369    fn first_free_map(&mut self) -> &mut [u8] {
370        &mut self.free_map
371    }
372
373    fn first_free_page_map(&mut self) -> &mut [u8] {
374        &mut self.free_page_map
375    }
376}
377
378#[derive(Clone, Debug)]
379pub struct AnsiHeader {
380    next_block: AnsiBlockId,
381    next_page: AnsiPageId,
382    unique: u32,
383    nids: [u32; 32],
384    root: AnsiRoot,
385    free_map: [u8; 128],
386    free_page_map: [u8; 128],
387    crypt_method: NdbCryptMethod,
388
389    reserved1: u32,
390    reserved2: u32,
391    reserved3: [u8; 36],
392}
393
394impl AnsiHeader {
395    pub fn new(root: AnsiRoot, crypt_method: NdbCryptMethod) -> Self {
396        Self {
397            next_block: AnsiBlockId::from(4),
398            next_page: AnsiPageId::from(1),
399            unique: 0,
400            nids: NDB_DEFAULT_NIDS,
401            root,
402            free_map: [0xFF; 128],
403            free_page_map: [0xFF; 128],
404            crypt_method,
405            reserved1: 0,
406            reserved2: 0,
407            reserved3: [0; 36],
408        }
409    }
410}
411
412impl Header<AnsiPstFile> for AnsiHeader {
413    fn version(&self) -> NdbVersion {
414        NdbVersion::Ansi
415    }
416
417    fn crypt_method(&self) -> NdbCryptMethod {
418        self.crypt_method
419    }
420
421    fn next_block(&self) -> <AnsiPstFile as PstFile>::BlockId {
422        self.next_block
423    }
424
425    fn next_page(&self) -> <AnsiPstFile as PstFile>::PageId {
426        self.next_page
427    }
428
429    fn unique_value(&self) -> u32 {
430        self.unique
431    }
432
433    fn root(&self) -> &<AnsiPstFile as PstFile>::Root {
434        &self.root
435    }
436
437    fn root_mut(&mut self) -> &mut <AnsiPstFile as PstFile>::Root {
438        &mut self.root
439    }
440}
441
442impl HeaderReadWrite<AnsiPstFile> for AnsiHeader {
443    fn read(f: &mut dyn Read) -> io::Result<Self> {
444        // dwMagic
445        let magic = f.read_u32::<LittleEndian>()?;
446        if magic != HEADER_MAGIC {
447            return Err(NdbError::InvalidNdbHeaderMagicValue(magic).into());
448        }
449
450        // dwCRCPartial
451        let crc_partial = f.read_u32::<LittleEndian>()?;
452
453        let mut crc_data = [0_u8; 504];
454        f.read_exact(&mut crc_data)?;
455        if crc_partial != compute_crc(0, &crc_data[..471]) {
456            return Err(NdbError::InvalidNdbHeaderPartialCrc(crc_partial).into());
457        }
458
459        let mut cursor = Cursor::new(crc_data);
460
461        // wMagicClient
462        let magic = cursor.read_u16::<LittleEndian>()?;
463        if magic != HEADER_MAGIC_CLIENT {
464            return Err(NdbError::InvalidNdbHeaderMagicClientValue(magic).into());
465        }
466
467        // wVer
468        let version = NdbVersion::try_from(cursor.read_u16::<LittleEndian>()?)?;
469        if version != NdbVersion::Ansi {
470            return Err(NdbError::UnicodePstVersion(version as u16).into());
471        }
472
473        // wVerClient
474        let version = cursor.read_u16::<LittleEndian>()?;
475        if version != NDB_CLIENT_VERSION {
476            return Err(NdbError::InvalidNdbHeaderClientVersion(version).into());
477        }
478
479        // bPlatformCreate
480        let platform_create = cursor.read_u8()?;
481        if platform_create != NDB_PLATFORM_CREATE {
482            return Err(NdbError::InvalidNdbHeaderPlatformCreate(platform_create).into());
483        }
484
485        // bPlatformAccess
486        let platform_access = cursor.read_u8()?;
487        if platform_access != NDB_PLATFORM_ACCESS {
488            return Err(NdbError::InvalidNdbHeaderPlatformAccess(platform_access).into());
489        }
490
491        // dwReserved1
492        let reserved1 = cursor.read_u32::<LittleEndian>()?;
493
494        // dwReserved2
495        let reserved2 = cursor.read_u32::<LittleEndian>()?;
496
497        // bidNextB
498        let next_block = AnsiBlockId::read(&mut cursor)?;
499
500        // bidNextP
501        let next_page = AnsiPageId::read(&mut cursor)?;
502
503        // dwUnique
504        let unique = cursor.read_u32::<LittleEndian>()?;
505
506        // rgnid
507        let mut nids = [0_u32; 32];
508        for nid in nids.iter_mut() {
509            *nid = cursor.read_u32::<LittleEndian>()?;
510        }
511
512        // root
513        let root = AnsiRoot::read(&mut cursor)?;
514
515        // rgbFM
516        let mut free_map = [0; 128];
517        cursor.read_exact(&mut free_map)?;
518
519        // rgbFP
520        let mut free_page_map = [0; 128];
521        cursor.read_exact(&mut free_page_map)?;
522
523        // bSentinel
524        let sentinel = cursor.read_u8()?;
525        if sentinel != NDB_SENTINEL {
526            return Err(NdbError::InvalidNdbHeaderSentinelValue(sentinel).into());
527        }
528
529        // bCryptMethod
530        let crypt_method = NdbCryptMethod::try_from(cursor.read_u8()?)?;
531
532        // rgbReserved
533        let reserved = cursor.read_u16::<LittleEndian>()?;
534        if reserved != 0 {
535            return Err(NdbError::InvalidNdbHeaderReservedValue(reserved).into());
536        }
537
538        // ullReserved, dwReserved (total 12 bytes)
539        let mut reserved = [0_u8; 12];
540        cursor.read_exact(&mut reserved)?;
541        if reserved != [0; 12] {
542            return Err(NdbError::InvalidNdbHeaderAnsiReservedBytes.into());
543        }
544
545        // rgbReserved2, bReserved, rgbReserved3 (total 36 bytes)
546        let mut reserved3 = [0_u8; 36];
547        cursor.read_exact(&mut reserved3)?;
548
549        Ok(Self {
550            next_page,
551            unique,
552            nids,
553            root,
554            free_map,
555            free_page_map,
556            crypt_method,
557            next_block,
558            reserved1,
559            reserved2,
560            reserved3,
561        })
562    }
563
564    fn write(&self, f: &mut dyn Write) -> io::Result<()> {
565        let mut cursor = Cursor::new([0_u8; 504]);
566        // wMagicClient
567        cursor.write_u16::<LittleEndian>(HEADER_MAGIC_CLIENT)?;
568        // wVer
569        cursor.write_u16::<LittleEndian>(NdbVersion::Ansi as u16)?;
570        // wVerClient
571        cursor.write_u16::<LittleEndian>(NDB_CLIENT_VERSION)?;
572        // bPlatformCreate
573        cursor.write_u8(NDB_PLATFORM_CREATE)?;
574        // bPlatformAccess
575        cursor.write_u8(NDB_PLATFORM_ACCESS)?;
576        // dwReserved1
577        cursor.write_u32::<LittleEndian>(self.reserved1)?;
578        // dwReserved2
579        cursor.write_u32::<LittleEndian>(self.reserved2)?;
580        // bidNextB
581        self.next_block.write(&mut cursor)?;
582        // bidNextP
583        self.next_page.write(&mut cursor)?;
584        // dwUnique
585        cursor.write_u32::<LittleEndian>(self.unique)?;
586        // rgnid
587        for nid in self.nids.iter() {
588            cursor.write_u32::<LittleEndian>(*nid)?;
589        }
590        // root
591        self.root.write(&mut cursor)?;
592        // rgbFM
593        cursor.write_all(&self.free_map)?;
594        // rgbFP
595        cursor.write_all(&self.free_page_map)?;
596        // bSentinel
597        cursor.write_u8(NDB_SENTINEL)?;
598        // bCryptMethod
599        cursor.write_u8(self.crypt_method as u8)?;
600        // rgbReserved
601        cursor.write_u16::<LittleEndian>(0)?;
602        // ullReserved, dwReserved (total 12 bytes)
603        cursor.write_all(&[0_u8; 12])?;
604        // rgbReserved2, bReserved, rgbReserved3 (total 36 bytes)
605        cursor.write_all(&self.reserved3)?;
606
607        let crc_data = cursor.into_inner();
608        let crc_partial = compute_crc(0, &crc_data[..471]);
609
610        // dwMagic
611        f.write_u32::<LittleEndian>(HEADER_MAGIC)?;
612        // dwCRCPartial
613        f.write_u32::<LittleEndian>(crc_partial)?;
614
615        f.write_all(&crc_data)
616    }
617
618    fn update_unique(&mut self) {
619        self.unique = self.unique.wrapping_add(1);
620    }
621
622    fn first_free_map(&mut self) -> &mut [u8] {
623        &mut self.free_map
624    }
625
626    fn first_free_page_map(&mut self) -> &mut [u8] {
627        &mut self.free_page_map
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634
635    #[test]
636    fn test_magic_values() {
637        assert_eq!(HEADER_MAGIC, 0x4E444221);
638        assert_eq!(HEADER_MAGIC_CLIENT, 0x4D53);
639    }
640}