1use std::{
16 borrow::Cow,
17 fmt,
18 ops::{Deref, DerefMut, Range},
19};
20
21use {
22 super::{BLCKSZ, BlockNumber, checksum::checksum_page},
23 crate::{TransactionId, XLogRecPtr},
24};
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Page {
29 pub bytes: [u8; BLCKSZ],
30}
31
32impl Deref for Page {
33 type Target = [u8];
34
35 fn deref(&self) -> &Self::Target {
36 &self.bytes
37 }
38}
39
40impl DerefMut for Page {
41 fn deref_mut(&mut self) -> &mut Self::Target {
42 &mut self.bytes
43 }
44}
45
46pub struct ValidatedPage<'a> {
47 page: Cow<'a, Page>,
48 block: BlockNumber,
49}
50
51impl ValidatedPage<'_> {
52 pub fn page(&self) -> &Page {
53 &self.page
54 }
55
56 pub fn block(&self) -> BlockNumber {
58 self.block
59 }
60}
61
62pub const HEADER_SIZE: usize = 24;
63
64pub const PAGE_LAYOUT_VERSION: u16 = 4;
67
68pub(crate) const PD_CHECKSUM: Range<usize> = 8..10;
70
71const MAXIMUM_ALIGNOF: usize = 8;
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
76pub struct LocationIndex(pub u16);
77
78impl From<LocationIndex> for usize {
79 fn from(location: LocationIndex) -> Self {
80 usize::from(location.0)
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct Header {
86 pub lsn: Option<XLogRecPtr>,
90 pub checksum: u16,
91 pub flags: PageFlags,
92 pub lower: LocationIndex,
94 pub upper: LocationIndex,
96 pub special: LocationIndex,
99 pub pagesize_version: u16,
101 pub prune_xid: Option<TransactionId>,
106}
107
108impl Page {
109 pub fn header(&self) -> Result<Header, InvalidHeader> {
111 Header::try_from_raw(self.header_raw())
112 }
113
114 pub fn header_raw(&self) -> &[u8; HEADER_SIZE] {
116 self.bytes.first_chunk().unwrap()
117 }
118
119 pub fn init(page: &mut Page, special_size: usize) {
122 let special_size = special_size.next_multiple_of(MAXIMUM_ALIGNOF);
123 assert!(
124 HEADER_SIZE + special_size <= BLCKSZ,
125 "special space too large"
126 );
127 let special = LocationIndex((BLCKSZ - special_size) as u16);
128 page.fill(0);
129 page.set_header(Header {
130 lsn: None,
131 checksum: 0,
132 flags: PageFlags::default(),
133 lower: LocationIndex(HEADER_SIZE as u16),
134 upper: special,
135 special,
136 pagesize_version: BLCKSZ as u16 | PAGE_LAYOUT_VERSION,
137 prune_xid: None,
138 });
139 }
140
141 pub fn is_uninitialized(&self) -> bool {
142 read_u16(self, 14) == 0
144 }
145
146 pub fn set_header(&mut self, header: Header) {
147 let lsn = header.lsn.map_or(0, u64::from);
148 write_u32(self, 0, (lsn >> 32) as u32);
149 write_u32(self, 4, lsn as u32);
150 write_u16(self, 8, header.checksum);
151 write_u16(self, 10, header.flags.into());
152 write_u16(self, 12, header.lower.0);
153 write_u16(self, 14, header.upper.0);
154 write_u16(self, 16, header.special.0);
155 write_u16(self, 18, header.pagesize_version);
156 write_u32(self, 20, header.prune_xid.map_or(0, u32::from));
157 }
158
159 pub fn try_set_checksum_for_block(
161 mut self,
162 block: BlockNumber,
163 ) -> Result<ValidatedPage<'static>, PageError> {
164 if self.is_uninitialized() {
165 return if self.iter().all(|&byte| byte == 0) {
166 Ok(ValidatedPage {
167 page: Cow::Owned(self),
168 block,
169 })
170 } else {
171 Err(PageError::InvalidHeader { block })
172 };
173 }
174
175 if self.header().is_err() {
176 return Err(PageError::InvalidHeader { block });
177 }
178
179 let checksum = checksum_page(&self, block);
180 self[PD_CHECKSUM].copy_from_slice(&checksum.to_le_bytes());
181 Ok(ValidatedPage {
182 page: Cow::Owned(self),
183 block,
184 })
185 }
186
187 pub fn try_validated_for_block<'a>(
190 &'a self,
191 block: BlockNumber,
192 ) -> Result<ValidatedPage<'a>, PageError> {
193 if self.is_uninitialized() {
194 return if self.iter().all(|&byte| byte == 0) {
195 Ok(ValidatedPage {
196 page: Cow::Borrowed(self),
197 block,
198 })
199 } else {
200 Err(PageError::InvalidHeader { block })
201 };
202 }
203 let stored = read_u16(self, PD_CHECKSUM.start);
206 let computed = checksum_page(self, block);
207 if stored != computed {
208 return Err(PageError::ChecksumMismatch {
209 block,
210 stored,
211 computed,
212 });
213 }
214 if self.header().is_err() {
215 return Err(PageError::InvalidHeader { block });
216 }
217 Ok(ValidatedPage {
218 page: Cow::Borrowed(self),
219 block,
220 })
221 }
222}
223
224impl Header {
225 pub fn try_from_raw(raw: &[u8; HEADER_SIZE]) -> Result<Self, InvalidHeader> {
228 let lsn = (u64::from(read_u32(raw, 0)) << 32) | u64::from(read_u32(raw, 4));
230 let header = Header {
231 lsn: XLogRecPtr::from_raw(lsn),
232 checksum: read_u16(raw, 8),
233 flags: PageFlags::try_from(read_u16(raw, 10))?,
234 lower: LocationIndex(read_u16(raw, 12)),
235 upper: LocationIndex(read_u16(raw, 14)),
236 special: LocationIndex(read_u16(raw, 16)),
237 pagesize_version: read_u16(raw, 18),
238 prune_xid: TransactionId::from_raw(read_u32(raw, 20)),
239 };
240 let Header {
241 lower,
242 upper,
243 special,
244 ..
245 } = header;
246 if !(lower <= upper
247 && upper <= special
248 && usize::from(special) <= BLCKSZ
249 && usize::from(special) % MAXIMUM_ALIGNOF == 0)
250 {
251 return Err(InvalidHeader::Offsets {
252 lower,
253 upper,
254 special,
255 });
256 }
257 Ok(header)
258 }
259}
260
261#[derive(Clone, Copy, Debug, PartialEq, Eq)]
264pub enum InvalidHeader {
265 Flags(u16),
267 Offsets {
270 lower: LocationIndex,
271 upper: LocationIndex,
272 special: LocationIndex,
273 },
274}
275
276impl fmt::Display for InvalidHeader {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 match self {
279 InvalidHeader::Flags(bits) => write!(f, "invalid page flags {bits:#06x}"),
280 InvalidHeader::Offsets {
281 lower,
282 upper,
283 special,
284 } => write!(
285 f,
286 "invalid page offsets: lower {}, upper {}, special {}",
287 lower.0, upper.0, special.0
288 ),
289 }
290 }
291}
292
293impl std::error::Error for InvalidHeader {}
294
295#[derive(Clone, Copy, Debug, PartialEq, Eq)]
296pub enum PageError {
297 ChecksumMismatch {
298 block: BlockNumber,
299 stored: u16,
300 computed: u16,
301 },
302 InvalidHeader {
303 block: BlockNumber,
304 },
305}
306
307impl fmt::Display for PageError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 PageError::ChecksumMismatch {
311 block,
312 stored,
313 computed,
314 } => write!(
315 f,
316 "page verification failed in block {block}: \
317 calculated checksum {computed} but expected {stored}"
318 ),
319 PageError::InvalidHeader { block } => write!(f, "invalid page header in block {block}"),
320 }
321 }
322}
323
324impl std::error::Error for PageError {}
325
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
328pub struct PageFlags {
329 pub has_free_lines: bool,
332 pub page_full: bool,
335 pub all_visible: bool,
339}
340
341const PD_HAS_FREE_LINES: u16 = 0x0001;
342const PD_PAGE_FULL: u16 = 0x0002;
343const PD_ALL_VISIBLE: u16 = 0x0004;
344const PD_VALID_FLAG_BITS: u16 = PD_HAS_FREE_LINES | PD_PAGE_FULL | PD_ALL_VISIBLE;
346
347impl TryFrom<u16> for PageFlags {
349 type Error = InvalidHeader;
350
351 fn try_from(bits: u16) -> Result<Self, Self::Error> {
352 if bits & !PD_VALID_FLAG_BITS != 0 {
353 return Err(InvalidHeader::Flags(bits));
354 }
355 Ok(PageFlags {
356 has_free_lines: bits & PD_HAS_FREE_LINES != 0,
357 page_full: bits & PD_PAGE_FULL != 0,
358 all_visible: bits & PD_ALL_VISIBLE != 0,
359 })
360 }
361}
362
363impl From<PageFlags> for u16 {
364 fn from(flags: PageFlags) -> Self {
365 let mut bits = 0;
366 if flags.has_free_lines {
367 bits |= PD_HAS_FREE_LINES;
368 }
369 if flags.page_full {
370 bits |= PD_PAGE_FULL;
371 }
372 if flags.all_visible {
373 bits |= PD_ALL_VISIBLE;
374 }
375 bits
376 }
377}
378
379fn read_u16(bytes: &[u8], offset: usize) -> u16 {
380 u16::from_le_bytes([bytes[offset], bytes[offset + 1]])
381}
382
383fn read_u32(bytes: &[u8], offset: usize) -> u32 {
384 u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
385}
386
387fn write_u16(page: &mut Page, offset: usize, value: u16) {
388 page[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
389}
390
391fn write_u32(page: &mut Page, offset: usize, value: u32) {
392 page[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 fn sample_page() -> Page {
401 let mut page = Page { bytes: [0; BLCKSZ] };
402 Page::init(&mut page, 16);
403 for (i, byte) in page[HEADER_SIZE..BLCKSZ - 16].iter_mut().enumerate() {
404 *byte = (i * 31 % 251) as u8;
405 }
406 page
407 }
408
409 fn sealed(page: Page, block: BlockNumber) -> Page {
411 let validated = page.try_set_checksum_for_block(block).unwrap();
412 validated.page().clone()
413 }
414
415 #[test]
416 fn init_matches_page_init() {
417 let mut page = Page {
418 bytes: [0xAB; BLCKSZ],
419 };
420 Page::init(&mut page, 12);
421 let header = page.header().unwrap();
422 assert_eq!(header.lower, LocationIndex(24));
423 assert_eq!(header.upper, LocationIndex(8192 - 16));
424 assert_eq!(header.special, LocationIndex(8192 - 16));
425 assert_eq!(header.pagesize_version, 8196);
426 assert_eq!(header.lsn, None);
427 assert_eq!(header.prune_xid, None);
428 assert!(page[HEADER_SIZE..].iter().all(|&byte| byte == 0));
429 }
430
431 #[test]
432 fn header_round_trips() {
433 let header = Header {
434 lsn: XLogRecPtr::from_raw(0x0000_0001_0A2B_3C4D),
435 checksum: 0x1234,
436 flags: PageFlags {
437 has_free_lines: true,
438 page_full: false,
439 all_visible: true,
440 },
441 lower: LocationIndex(40),
442 upper: LocationIndex(8000),
443 special: LocationIndex(8176),
444 pagesize_version: 8196,
445 prune_xid: TransactionId::from_raw(742),
446 };
447 let mut page = Page { bytes: [0; BLCKSZ] };
448 page.set_header(header);
449 assert_eq!(page.header(), Ok(header));
450 assert_eq!(page[..8], [0x01, 0, 0, 0, 0x4D, 0x3C, 0x2B, 0x0A]);
452 }
453
454 #[test]
455 fn all_zero_page_verifies() {
456 let page = Page { bytes: [0; BLCKSZ] };
458 assert!(page.try_validated_for_block(BlockNumber(7)).is_ok());
459 }
460
461 #[test]
462 fn set_checksum_leaves_a_new_page_all_zeros() {
463 let page = sealed(Page { bytes: [0; BLCKSZ] }, BlockNumber(7));
466 assert_eq!(page.bytes, [0; BLCKSZ]);
467 }
468
469 #[test]
470 fn checksummed_page_verifies() {
471 let page = sealed(sample_page(), BlockNumber(3));
472 assert_ne!(page.header().unwrap().checksum, 0);
473 assert!(page.try_validated_for_block(BlockNumber(3)).is_ok());
474 }
475
476 #[test]
477 fn every_flipped_byte_is_caught() {
478 let page = sealed(sample_page(), BlockNumber(0));
479 for i in 0..BLCKSZ {
480 let mut corrupt = page.clone();
481 corrupt[i] ^= 0xFF;
482 assert!(
483 corrupt.try_validated_for_block(BlockNumber(0)).is_err(),
484 "flipping byte {i} went unnoticed"
485 );
486 }
487 }
488
489 #[test]
490 fn page_at_the_wrong_block_is_caught() {
491 let page = sealed(sample_page(), BlockNumber(5));
492 for block in (0..1000u32).filter(|&block| block != 5) {
493 assert!(matches!(
494 page.try_validated_for_block(BlockNumber(block)),
495 Err(PageError::ChecksumMismatch { .. })
496 ));
497 }
498 }
499
500 #[test]
501 fn insane_header_is_caught_even_with_a_good_checksum() {
502 let mut page = sample_page();
503 let mut header = page.header().unwrap();
504 header.lower = LocationIndex(header.upper.0 + 8);
505 page.set_header(header);
506 assert!(matches!(page.header(), Err(InvalidHeader::Offsets { .. })));
507 let checksum = checksum_page(&page, BlockNumber(0));
509 page[PD_CHECKSUM].copy_from_slice(&checksum.to_le_bytes());
510 assert!(matches!(
511 page.try_validated_for_block(BlockNumber(0)),
512 Err(PageError::InvalidHeader {
513 block: BlockNumber(0)
514 })
515 ));
516 }
517
518 #[test]
519 fn unknown_flag_is_caught_even_with_a_good_checksum() {
520 let mut page = sample_page();
521 write_u16(&mut page, 10, 0x0008);
523 assert_eq!(page.header(), Err(InvalidHeader::Flags(0x0008)));
524 let checksum = checksum_page(&page, BlockNumber(0));
525 page[PD_CHECKSUM].copy_from_slice(&checksum.to_le_bytes());
526 assert!(matches!(
527 page.try_validated_for_block(BlockNumber(0)),
528 Err(PageError::InvalidHeader {
529 block: BlockNumber(0)
530 })
531 ));
532 }
533
534 #[test]
535 fn sealing_an_insane_header_fails() {
536 let mut page = sample_page();
537 let mut header = page.header().unwrap();
538 header.lower = LocationIndex(header.upper.0 + 8);
539 page.set_header(header);
540 assert_eq!(
541 page.try_set_checksum_for_block(BlockNumber(0)).err(),
542 Some(PageError::InvalidHeader {
543 block: BlockNumber(0)
544 })
545 );
546 }
547
548 #[test]
549 fn zero_upper_with_data_is_not_a_new_page() {
550 let mut page = Page { bytes: [0; BLCKSZ] };
551 page[100] = 1;
552 assert!(matches!(
553 page.try_validated_for_block(BlockNumber(0)),
554 Err(PageError::InvalidHeader {
555 block: BlockNumber(0)
556 })
557 ));
558 }
559}