1use citadel_core::{KEY_SIZE, REGION_STORE_BLOCK, WRAPPED_KEY_SIZE};
16use citadel_crypto::mac::{hmac_sha256, verify_hmac_sha256};
17
18pub(crate) const BLOCK: usize = REGION_STORE_BLOCK;
19pub(crate) const HEADER_MAC_INPUT: usize = 28;
21pub(crate) const SLOT_MAC_INPUT: usize = 60;
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
26pub enum SlotState {
27 Empty = 0,
28 Live = 1,
29 Tombstone = 2,
30}
31
32impl SlotState {
33 pub(crate) fn from_u32(v: u32) -> Option<Self> {
34 match v {
35 0 => Some(SlotState::Empty),
36 1 => Some(SlotState::Live),
37 2 => Some(SlotState::Tombstone),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug)]
46pub struct SlotRecord {
47 pub state: SlotState,
48 pub region_id: u64,
49 pub gen: u64,
50 pub wrapped: [u8; WRAPPED_KEY_SIZE],
51}
52
53pub(crate) fn header_offset(copy_b: bool) -> u64 {
55 if copy_b {
56 BLOCK as u64
57 } else {
58 0
59 }
60}
61
62pub(crate) fn slot_offset(i: u32, copy_b: bool) -> u64 {
64 let base = 2 * BLOCK as u64 + i as u64 * 2 * BLOCK as u64;
65 base + if copy_b { BLOCK as u64 } else { 0 }
66}
67
68pub(crate) fn build_header_block(
69 mac_key: &[u8; KEY_SIZE],
70 magic: u32,
71 version: u32,
72 file_id: u64,
73 slot_count: u32,
74 gen: u64,
75) -> [u8; BLOCK] {
76 let mut b = [0u8; BLOCK];
77 b[0..4].copy_from_slice(&magic.to_le_bytes());
78 b[4..8].copy_from_slice(&version.to_le_bytes());
79 b[8..16].copy_from_slice(&file_id.to_le_bytes());
80 b[16..20].copy_from_slice(&slot_count.to_le_bytes());
81 b[20..28].copy_from_slice(&gen.to_le_bytes());
82 let mac = hmac_sha256(mac_key, &b[0..HEADER_MAC_INPUT]);
83 b[HEADER_MAC_INPUT..HEADER_MAC_INPUT + 32].copy_from_slice(&mac);
84 b
85}
86
87pub(crate) fn parse_header_block(
90 mac_key: &[u8; KEY_SIZE],
91 magic: u32,
92 version: u32,
93 file_id: u64,
94 b: &[u8],
95) -> Option<(u32, u64)> {
96 if b.len() < HEADER_MAC_INPUT + 32 {
97 return None;
98 }
99 if u32::from_le_bytes(b[0..4].try_into().ok()?) != magic {
100 return None;
101 }
102 if u32::from_le_bytes(b[4..8].try_into().ok()?) != version {
103 return None;
104 }
105 let tag: [u8; 32] = b[HEADER_MAC_INPUT..HEADER_MAC_INPUT + 32].try_into().ok()?;
106 if !verify_hmac_sha256(mac_key, &b[0..HEADER_MAC_INPUT], &tag) {
107 return None;
108 }
109 if u64::from_le_bytes(b[8..16].try_into().ok()?) != file_id {
110 return None;
111 }
112 let slot_count = u32::from_le_bytes(b[16..20].try_into().ok()?);
113 let gen = u64::from_le_bytes(b[20..28].try_into().ok()?);
114 Some((slot_count, gen))
115}
116
117pub(crate) fn build_slot_block(
118 mac_key: &[u8; KEY_SIZE],
119 state: SlotState,
120 region_id: u64,
121 gen: u64,
122 wrapped: &[u8; WRAPPED_KEY_SIZE],
123) -> [u8; BLOCK] {
124 let mut b = [0u8; BLOCK];
125 b[0..4].copy_from_slice(&(state as u32).to_le_bytes());
126 b[4..12].copy_from_slice(®ion_id.to_le_bytes());
127 b[12..20].copy_from_slice(&gen.to_le_bytes());
128 b[20..20 + WRAPPED_KEY_SIZE].copy_from_slice(wrapped);
129 let mac = hmac_sha256(mac_key, &b[0..SLOT_MAC_INPUT]);
130 b[SLOT_MAC_INPUT..SLOT_MAC_INPUT + 32].copy_from_slice(&mac);
131 b
132}
133
134pub(crate) fn empty_slot_block(mac_key: &[u8; KEY_SIZE]) -> [u8; BLOCK] {
137 build_slot_block(mac_key, SlotState::Empty, 0, 0, &[0u8; WRAPPED_KEY_SIZE])
138}
139
140pub(crate) fn parse_slot_block(mac_key: &[u8; KEY_SIZE], b: &[u8]) -> Option<SlotRecord> {
141 if b.len() < SLOT_MAC_INPUT + 32 {
142 return None;
143 }
144 let tag: [u8; 32] = b[SLOT_MAC_INPUT..SLOT_MAC_INPUT + 32].try_into().ok()?;
145 if !verify_hmac_sha256(mac_key, &b[0..SLOT_MAC_INPUT], &tag) {
146 return None;
147 }
148 let state = SlotState::from_u32(u32::from_le_bytes(b[0..4].try_into().ok()?))?;
149 let region_id = u64::from_le_bytes(b[4..12].try_into().ok()?);
150 let gen = u64::from_le_bytes(b[12..20].try_into().ok()?);
151 let wrapped: [u8; WRAPPED_KEY_SIZE] = b[20..20 + WRAPPED_KEY_SIZE].try_into().ok()?;
152 Some(SlotRecord {
153 state,
154 region_id,
155 gen,
156 wrapped,
157 })
158}