1use core::fmt;
11
12use crate::tag::{DecodedTaskTag, TaskKind, decode_task_tag};
13
14pub const SEED_TASKS: usize = 1;
16pub const PUBLIC_SEED_TASKS: usize = 1;
18pub const MASK_TASKS: usize = 16;
20pub const SEGMENT_PRF_TASKS: usize = 67;
22pub const SECRET_DATA_TASKS: usize = 67;
24pub const SECRET_PADDING_TASKS: usize = 67;
26pub const CHAIN_TASKS: usize = 1_005;
28pub const PUBLIC_KEY_TASKS: usize = 34;
30pub const TOTAL_TASKS: usize = SEED_TASKS
32 + PUBLIC_SEED_TASKS
33 + MASK_TASKS
34 + SEGMENT_PRF_TASKS
35 + SECRET_DATA_TASKS
36 + SECRET_PADDING_TASKS
37 + CHAIN_TASKS
38 + PUBLIC_KEY_TASKS;
39
40#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
42pub struct TaskCounts {
43 pub seed: u16,
45 pub public_seed: u16,
47 pub mask: u16,
49 pub segment_prf: u16,
51 pub secret_data: u16,
53 pub secret_padding: u16,
55 pub chain: u16,
57 pub public_key: u16,
59}
60
61impl TaskCounts {
62 pub const EXPECTED: Self = Self {
64 seed: 1,
65 public_seed: 1,
66 mask: 16,
67 segment_prf: 67,
68 secret_data: 67,
69 secret_padding: 67,
70 chain: 1_005,
71 public_key: 34,
72 };
73
74 #[must_use]
76 pub const fn total(self) -> u16 {
77 self.seed
78 + self.public_seed
79 + self.mask
80 + self.segment_prf
81 + self.secret_data
82 + self.secret_padding
83 + self.chain
84 + self.public_key
85 }
86
87 pub fn record(&mut self, kind: TaskKind) {
89 let counter = match kind {
90 TaskKind::Seed => &mut self.seed,
91 TaskKind::PublicSeed => &mut self.public_seed,
92 TaskKind::Mask => &mut self.mask,
93 TaskKind::SegmentPrf => &mut self.segment_prf,
94 TaskKind::SecretData => &mut self.secret_data,
95 TaskKind::SecretPadding => &mut self.secret_padding,
96 TaskKind::Chain => &mut self.chain,
97 TaskKind::PublicKey => &mut self.public_key,
98 };
99 *counter = counter.saturating_add(1);
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct TaskAudit {
106 pub counts: TaskCounts,
108 pub context: u8,
110 pub generation: u8,
112}
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum TaskTraceError {
117 Malformed {
119 index: usize,
121 raw: u32,
123 },
124 StaleGeneration {
126 index: usize,
128 expected: u8,
130 observed: u8,
132 },
133 WrongContext {
135 index: usize,
137 expected: u8,
139 observed: u8,
141 },
142 UnexpectedTask {
144 index: usize,
146 expected: DecodedTaskTag,
148 observed: DecodedTaskTag,
150 },
151 Truncated {
153 expected: usize,
155 observed: usize,
157 },
158 Extra {
160 index: usize,
162 raw: u32,
164 },
165 CountMismatch {
167 expected: TaskCounts,
169 observed: TaskCounts,
171 },
172}
173
174impl fmt::Display for TaskTraceError {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 Self::Malformed { index, raw } => {
178 write!(
179 formatter,
180 "malformed task tag {raw:#010x} at trace index {index}"
181 )
182 }
183 Self::StaleGeneration {
184 index,
185 expected,
186 observed,
187 } => write!(
188 formatter,
189 "generation {observed} at trace index {index}, expected {expected}"
190 ),
191 Self::WrongContext {
192 index,
193 expected,
194 observed,
195 } => write!(
196 formatter,
197 "context {observed} at trace index {index}, expected {expected}"
198 ),
199 Self::UnexpectedTask { index, .. } => {
200 write!(
201 formatter,
202 "task dependency-order mismatch at trace index {index}"
203 )
204 }
205 Self::Truncated { expected, observed } => {
206 write!(
207 formatter,
208 "truncated task trace: {observed} entries, expected {expected}"
209 )
210 }
211 Self::Extra { index, raw } => {
212 write!(
213 formatter,
214 "extra task tag {raw:#010x} at trace index {index}"
215 )
216 }
217 Self::CountMismatch { expected, observed } => write!(
218 formatter,
219 "task-class count mismatch: observed {observed:?}, expected {expected:?}"
220 ),
221 }
222 }
223}
224
225impl std::error::Error for TaskTraceError {}
226
227fn canonical_tag(
228 context: u8,
229 generation: u8,
230 kind: TaskKind,
231 segment: u8,
232 chain_step: u8,
233 block_index: u8,
234) -> DecodedTaskTag {
235 DecodedTaskTag::new(context, kind, segment, chain_step, block_index, generation)
236 .expect("the canonical task builder uses protocol bounds")
237}
238
239#[must_use]
248pub fn canonical_task_trace(context: u8, generation: u8) -> Vec<DecodedTaskTag> {
249 assert!(
250 context < 16,
251 "a scheduler context must fit the four-bit tag field"
252 );
253 let mut tasks = Vec::with_capacity(TOTAL_TASKS);
254 tasks.push(canonical_tag(context, generation, TaskKind::Seed, 0, 0, 0));
255 tasks.push(canonical_tag(
256 context,
257 generation,
258 TaskKind::PublicSeed,
259 0,
260 0,
261 0,
262 ));
263 for mask in 0_u8..16 {
264 tasks.push(canonical_tag(
265 context,
266 generation,
267 TaskKind::Mask,
268 mask,
269 0,
270 0,
271 ));
272 }
273 for segment in 0_u8..67 {
274 tasks.push(canonical_tag(
275 context,
276 generation,
277 TaskKind::SegmentPrf,
278 segment,
279 0,
280 0,
281 ));
282 tasks.push(canonical_tag(
283 context,
284 generation,
285 TaskKind::SecretData,
286 segment,
287 0,
288 0,
289 ));
290 tasks.push(canonical_tag(
291 context,
292 generation,
293 TaskKind::SecretPadding,
294 segment,
295 0,
296 1,
297 ));
298 for step in 1_u8..16 {
299 tasks.push(canonical_tag(
300 context,
301 generation,
302 TaskKind::Chain,
303 segment,
304 step,
305 0,
306 ));
307 }
308 }
309 for block in 0_u8..34 {
310 tasks.push(canonical_tag(
311 context,
312 generation,
313 TaskKind::PublicKey,
314 0,
315 0,
316 block,
317 ));
318 }
319 assert_eq!(tasks.len(), TOTAL_TASKS);
320 tasks
321}
322
323pub fn audit_task_trace(
341 raw_tasks: &[u32],
342 context: u8,
343 generation: u8,
344) -> Result<TaskAudit, TaskTraceError> {
345 let expected = canonical_task_trace(context, generation);
346 let common = raw_tasks.len().min(expected.len());
347 let mut counts = TaskCounts::default();
348 for index in 0..common {
349 let raw = raw_tasks[index];
350 let observed =
351 decode_task_tag(raw).map_err(|_| TaskTraceError::Malformed { index, raw })?;
352 if observed.generation != generation {
353 return Err(TaskTraceError::StaleGeneration {
354 index,
355 expected: generation,
356 observed: observed.generation,
357 });
358 }
359 if observed.context != context {
360 return Err(TaskTraceError::WrongContext {
361 index,
362 expected: context,
363 observed: observed.context,
364 });
365 }
366 if observed != expected[index] {
367 return Err(TaskTraceError::UnexpectedTask {
368 index,
369 expected: expected[index],
370 observed,
371 });
372 }
373 counts.record(observed.kind);
374 }
375 if raw_tasks.len() < expected.len() {
376 return Err(TaskTraceError::Truncated {
377 expected: expected.len(),
378 observed: raw_tasks.len(),
379 });
380 }
381 if raw_tasks.len() > expected.len() {
382 return Err(TaskTraceError::Extra {
383 index: expected.len(),
384 raw: raw_tasks[expected.len()],
385 });
386 }
387 if counts != TaskCounts::EXPECTED {
388 return Err(TaskTraceError::CountMismatch {
389 expected: TaskCounts::EXPECTED,
390 observed: counts,
391 });
392 }
393 Ok(TaskAudit {
394 counts,
395 context,
396 generation,
397 })
398}
399
400const _: () = {
401 assert!(CHAIN_TASKS == 67 * 15);
402 assert!(TOTAL_TASKS == 1_258);
403 assert!(TaskCounts::EXPECTED.total() == 1_258_u16);
404};