1use crate::block_utils::RefBlockChoice;
2use crate::cli_utils::setup_ctrlc_handler;
3use crate::file_reader::{FileReader, FileReaderParam};
4use crate::file_utils;
5use crate::general_error::Error;
6use crate::hash_stats::HashStats;
7use crate::json_printer::{BracketType, JSONPrinter};
8use crate::misc_utils;
9use crate::misc_utils::RequiredLenAndSeekTo;
10use crate::misc_utils::{PositionOrLength, RangeEnd};
11use crate::multihash::*;
12use crate::progress_report::*;
13use crate::sbx_block;
14use crate::sbx_block::{Block, BlockType};
15use crate::sbx_container_content;
16use crate::sbx_specs::Version;
17use crate::sbx_specs::{ver_to_block_size, ver_to_usize, SBX_LARGEST_BLOCK_SIZE};
18use crate::time_utils;
19use std::fmt;
20use std::io::SeekFrom;
21use std::sync::atomic::AtomicBool;
22use std::sync::{Arc, Mutex};
23
24pub enum HashAction {
25 NoHash,
26 HashAfterCheck,
27 HashOnly,
28}
29
30pub struct Param {
31 ref_block_choice: RefBlockChoice,
32 ref_block_from_pos: Option<u64>,
33 ref_block_to_pos: Option<RangeEnd<u64>>,
34 guess_burst_from_pos: Option<u64>,
35 report_blank: bool,
36 json_printer: Arc<JSONPrinter>,
37 from_pos: Option<u64>,
38 to_pos: Option<RangeEnd<u64>>,
39 force_misalign: bool,
40 hash_action: HashAction,
41 burst: Option<usize>,
42 in_file: String,
43 verbose: bool,
44 pr_verbosity_level: PRVerbosityLevel,
45}
46
47impl Param {
48 pub fn new(
49 ref_block_choice: RefBlockChoice,
50 ref_block_from_pos: Option<u64>,
51 ref_block_to_pos: Option<RangeEnd<u64>>,
52 guess_burst_from_pos: Option<u64>,
53 report_blank: bool,
54 json_printer: &Arc<JSONPrinter>,
55 from_pos: Option<u64>,
56 to_pos: Option<RangeEnd<u64>>,
57 force_misalign: bool,
58 hash_action: HashAction,
59 burst: Option<usize>,
60 in_file: &str,
61 verbose: bool,
62 pr_verbosity_level: PRVerbosityLevel,
63 ) -> Param {
64 Param {
65 ref_block_choice,
66 ref_block_from_pos,
67 ref_block_to_pos,
68 guess_burst_from_pos,
69 report_blank,
70 json_printer: Arc::clone(json_printer),
71 from_pos,
72 to_pos,
73 force_misalign,
74 hash_action,
75 burst,
76 in_file: String::from(in_file),
77 verbose,
78 pr_verbosity_level,
79 }
80 }
81}
82
83#[derive(Clone, Debug)]
84struct CheckStats {
85 block_size: u64,
86 pub meta_or_par_blocks_decoded: u64,
87 pub data_or_par_blocks_decoded: u64,
88 pub blocks_decode_failed: u64,
89 pub okay_blank_blocks: u64,
90 total_blocks: u64,
91 start_time: f64,
92 end_time: f64,
93}
94
95impl CheckStats {
96 pub fn new(ref_block: &Block, required_len: u64) -> Self {
97 use crate::file_utils::from_container_size::calc_total_block_count;
98
99 let total_blocks = calc_total_block_count(ref_block.get_version(), required_len);
100
101 CheckStats {
102 block_size: ver_to_block_size(ref_block.get_version()) as u64,
103 meta_or_par_blocks_decoded: 0,
104 data_or_par_blocks_decoded: 0,
105 blocks_decode_failed: 0,
106 okay_blank_blocks: 0,
107 total_blocks,
108 start_time: 0.,
109 end_time: 0.,
110 }
111 }
112
113 fn blocks_so_far(&self) -> u64 {
114 self.meta_or_par_blocks_decoded
115 + self.data_or_par_blocks_decoded
116 + self.blocks_decode_failed
117 + self.okay_blank_blocks
118 }
119}
120
121#[derive(Clone, Debug)]
122pub struct Stats {
123 version: Version,
124 check_stats: Option<CheckStats>,
125 do_hash: bool,
126 recorded_hash: Option<HashBytes>,
127 hash_result: Option<Result<(HashStats, HashBytes), Error>>,
128 json_printer: Arc<JSONPrinter>,
129}
130
131impl Stats {
132 pub fn new(ref_block: &Block, do_hash: bool, json_printer: &Arc<JSONPrinter>) -> Stats {
133 Stats {
134 version: ref_block.get_version(),
135 check_stats: None,
136 do_hash,
137 recorded_hash: None,
138 hash_result: None,
139 json_printer: Arc::clone(json_printer),
140 }
141 }
142}
143
144impl ProgressReport for CheckStats {
145 fn start_time_mut(&mut self) -> &mut f64 {
146 &mut self.start_time
147 }
148
149 fn end_time_mut(&mut self) -> &mut f64 {
150 &mut self.end_time
151 }
152
153 fn units_so_far(&self) -> u64 {
154 self.blocks_so_far() * self.block_size
155 }
156
157 fn total_units(&self) -> Option<u64> {
158 Some(self.total_blocks * self.block_size)
159 }
160}
161
162impl fmt::Display for Stats {
163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 let block_size = ver_to_block_size(self.version);
165 let check_time_elapsed = match &self.check_stats {
166 None => 0i64,
167 Some(stats) => (stats.end_time - stats.start_time) as i64,
168 };
169 let hash_time_elapsed = match &self.hash_result {
170 None => 0i64,
171 Some(Ok((stats, _))) => (stats.end_time - stats.start_time) as i64,
172 Some(Err(_)) => 0i64,
173 };
174 let time_elapsed = check_time_elapsed + hash_time_elapsed;
175
176 let json_printer = &self.json_printer;
177
178 json_printer.write_open_bracket(f, Some("stats"), BracketType::Curly)?;
179
180 write_maybe_json!(
181 f,
182 json_printer,
183 "SBX version : {}",
184 ver_to_usize(self.version)
185 )?;
186 if let Some(check_stats) = &self.check_stats {
187 write_maybe_json!(
188 f,
189 json_printer,
190 "Block size used in checking : {}",
191 block_size
192 )?;
193 write_maybe_json!(
194 f,
195 json_printer,
196 "Number of blocks processed : {}",
197 check_stats.blocks_so_far()
198 )?;
199 write_maybe_json!(
200 f,
201 json_printer,
202 "Number of blocks passed check (metadata) : {}",
203 check_stats.meta_or_par_blocks_decoded
204 )?;
205 write_maybe_json!(
206 f,
207 json_printer,
208 "Number of blocks passed check (data) : {}",
209 check_stats.data_or_par_blocks_decoded
210 )?;
211 write_maybe_json!(
212 f,
213 json_printer,
214 "Number of blocks failed check : {}",
215 check_stats.blocks_decode_failed
216 )?;
217
218 let (hour, minute, second) = time_utils::seconds_to_hms(check_time_elapsed);
219 write_maybe_json!(
220 f,
221 json_printer,
222 "Time elapsed for block check : {:02}:{:02}:{:02}",
223 hour,
224 minute,
225 second
226 )?;
227 }
228 if self.do_hash {
229 write_maybe_json!(
230 f,
231 json_printer,
232 "Recorded hash : {}",
233 match &self.recorded_hash {
234 None => null_if_json_else_NA!(json_printer).to_string(),
235 Some(h) => format!(
236 "{} - {}",
237 hash_type_to_string(h.0),
238 misc_utils::bytes_to_lower_hex_string(&h.1)
239 ),
240 }
241 )?;
242 write_maybe_json!(
243 f,
244 json_printer,
245 "Hash of stored data : {}",
246 match (&self.recorded_hash, &self.hash_result) {
247 (None, None) => null_if_json_else_NA!(json_printer).to_string(),
248 (Some(_), None) => null_if_json_else!(
249 json_printer,
250 "N/A - recorded hash type is not supported by blkar"
251 )
252 .to_string(),
253 (_, Some(Ok((_, h)))) => format!(
254 "{} - {}",
255 hash_type_to_string(h.0),
256 misc_utils::bytes_to_lower_hex_string(&h.1)
257 ),
258 (_, Some(Err(e))) => format!("{}", e),
259 }
260 )?;
261
262 let (hour, minute, second) = time_utils::seconds_to_hms(hash_time_elapsed);
263 write_maybe_json!(
264 f,
265 json_printer,
266 "Time elapsed for hashing : {:02}:{:02}:{:02}",
267 hour,
268 minute,
269 second
270 )?;
271 }
272
273 let (hour, minute, second) = time_utils::seconds_to_hms(time_elapsed);
274
275 write_maybe_json!(
276 f,
277 json_printer,
278 "Time elapsed : {:02}:{:02}:{:02}",
279 hour,
280 minute,
281 second
282 )?;
283 if self.do_hash {
284 match (&self.recorded_hash, &self.hash_result) {
285 (Some(recorded_hash), Some(Ok((_, computed_hash)))) => {
286 if recorded_hash.1 == computed_hash.1 {
287 write_if!(not_json => f, json_printer => "The hash of stored data matches the recorded hash";)?;
288 } else {
289 write_if!(not_json => f, json_printer => "The hash of stored data does NOT match the recorded hash";)?;
290 }
291 }
292 (Some(_), Some(Err(e))) => {
293 write_if!(not_json => f, json_printer => "Encountered error while hashing stored data, {}", e;)?;
294 }
295 (Some(_), None) => {
296 write_if!(not_json => f, json_printer => "No hash is available for stored data";)?;
297 }
298 (None, Some(_)) => {
299 write_if!(not_json => f, json_printer => "No recorded hash is available";)?;
300 }
301 (None, None) => {
302 write_if!(not_json => f, json_printer => "Neither recorded hash nor hash of stored data is available";)?;
303 }
304 }
305 }
306
307 json_printer.write_close_bracket(f)?;
308
309 Ok(())
310 }
311}
312
313fn check_blocks(
314 param: &Param,
315 ctrlc_stop_flag: &AtomicBool,
316 required_len: u64,
317 seek_to: u64,
318 ref_block: &Block,
319) -> Result<CheckStats, Error> {
320 let stats = Arc::new(Mutex::new(CheckStats::new(ref_block, required_len)));
321
322 let json_printer = ¶m.json_printer;
323
324 let version = ref_block.get_version();
325
326 let mut buffer: [u8; SBX_LARGEST_BLOCK_SIZE] = [0; SBX_LARGEST_BLOCK_SIZE];
327
328 let mut reader = FileReader::new(
329 ¶m.in_file,
330 FileReaderParam {
331 write: false,
332 buffered: true,
333 },
334 )?;
335
336 let mut block = Block::dummy();
337
338 let reporter = Arc::new(ProgressReporter::new(
339 &stats,
340 "SBX block checking progress",
341 "bytes",
342 param.pr_verbosity_level,
343 param.json_printer.json_enabled(),
344 ));
345
346 let ver_usize = ver_to_usize(version);
347
348 let block_size = ver_to_block_size(version);
349
350 let mut block_pos: u64;
351 let mut bytes_processed: u64 = 0;
352
353 let header_pred = header_pred_same_ver_uid!(ref_block);
354
355 reporter.start();
356
357 reader.seek(SeekFrom::Start(seek_to))?;
359
360 if param.verbose {
361 json_printer.print_open_bracket(Some("blocks failed"), BracketType::Square);
362 }
363
364 loop {
365 let mut stats = stats.lock().unwrap();
366
367 break_if_atomic_bool!(ctrlc_stop_flag);
368
369 break_if_reached_required_len!(bytes_processed, required_len);
370
371 let read_res = reader.read(sbx_block::slice_buf_mut(version, &mut buffer))?;
372
373 block_pos = bytes_processed;
374 bytes_processed += read_res.len_read as u64;
375
376 break_if_eof_seen!(read_res);
377
378 match block.sync_from_buffer(&buffer, Some(&header_pred), None) {
379 Ok(_) => match block.block_type() {
380 BlockType::Meta => {
381 stats.meta_or_par_blocks_decoded += 1;
382 }
383 BlockType::Data => {
384 stats.data_or_par_blocks_decoded += 1;
385 }
386 },
387 Err(_) => {
388 if misc_utils::buffer_is_blank(sbx_block::slice_buf(version, &buffer)) {
391 if param.report_blank {
392 if json_printer.json_enabled() {
393 if param.verbose {
394 json_printer.print_open_bracket(None, BracketType::Curly);
395
396 print_maybe_json!(json_printer, "pos : {}", block_pos);
397
398 json_printer.print_close_bracket();
399 }
400 } else {
401 print_if!(verbose => param, reporter =>
402 "Block failed check, version : {}, block size : {}, at byte {} (0x{:X})",
403 ver_usize,
404 block_size,
405 block_pos,
406 block_pos;);
407 }
408
409 stats.blocks_decode_failed += 1;
410 } else {
411 stats.okay_blank_blocks += 1;
412 }
413 } else {
414 stats.blocks_decode_failed += 1;
415 }
416 }
417 }
418 }
419
420 if param.verbose {
421 json_printer.print_close_bracket();
422 }
423
424 if stats.lock().unwrap().blocks_decode_failed > 0 {
425 print_if!(verbose not_json => param, reporter, json_printer => "";);
426 }
427
428 reporter.stop();
429
430 let stats = stats.lock().unwrap().clone();
431
432 Ok(stats)
433}
434
435fn hash(
436 param: &Param,
437 ctrlc_stop_flag: &Arc<AtomicBool>,
438 orig_file_size: u64,
439 ref_block_pos: u64,
440 ref_block: &Block,
441 hash_ctx: hash::Ctx,
442) -> Result<(HashStats, HashBytes), Error> {
443 let data_par_burst = get_data_par_burst!(param, ref_block_pos, ref_block, "check");
444
445 sbx_container_content::hash(
446 ¶m.json_printer,
447 param.pr_verbosity_level,
448 data_par_burst,
449 ctrlc_stop_flag,
450 ¶m.in_file,
451 orig_file_size,
452 ref_block,
453 hash_ctx,
454 )
455}
456
457pub fn check_file(param: &Param) -> Result<Option<Stats>, Error> {
458 let ctrlc_stop_flag = setup_ctrlc_handler(param.json_printer.json_enabled());
459
460 let (ref_block_pos, ref_block) = get_ref_block!(param, ¶m.json_printer, ctrlc_stop_flag);
461
462 let file_size = file_utils::get_file_size(¶m.in_file)?;
463
464 let RequiredLenAndSeekTo {
466 required_len,
467 seek_to,
468 } = misc_utils::calc_required_len_and_seek_to_from_byte_range(
469 param.from_pos,
470 param.to_pos,
471 param.force_misalign,
472 0,
473 PositionOrLength::Len(file_size),
474 Some(ver_to_block_size(ref_block.get_version()) as u64),
475 );
476
477 let do_check = match param.hash_action {
478 HashAction::HashOnly => false,
479 _ => true,
480 };
481
482 let do_hash = match param.hash_action {
483 HashAction::NoHash => false,
484 _ => true,
485 };
486
487 let mut stats = Stats::new(&ref_block, do_hash, ¶m.json_printer);
488
489 let (orig_file_size, hash_ctx) = if do_hash {
490 if ref_block.is_data() {
491 return Err(Error::with_msg("Reference block is not a metadata block"));
492 } else {
493 let orig_file_size = match ref_block.get_FSZ().unwrap() {
494 None => {
495 return Err(Error::with_msg(
496 "Reference block does not have a file size field",
497 ))
498 }
499 Some(x) => x,
500 };
501
502 let hash_ctx = match ref_block.get_HSH().unwrap() {
503 None => {
504 return Err(Error::with_msg(
505 "Reference block does not have a hash field",
506 ))
507 }
508 Some((ht, hsh)) => match hash::Ctx::new(*ht) {
509 Err(()) => return Err(Error::with_msg("Unsupported hash algorithm")),
510 Ok(ctx) => {
511 stats.recorded_hash = Some((*ht, hsh.clone()));
512 ctx
513 }
514 },
515 };
516
517 (Some(orig_file_size), Some(hash_ctx))
518 }
519 } else {
520 (None, None)
521 };
522
523 if do_check {
524 stats.check_stats = Some(check_blocks(
525 param,
526 &ctrlc_stop_flag,
527 required_len,
528 seek_to,
529 &ref_block,
530 )?)
531 }
532
533 if do_hash {
534 let hash_result = hash(
535 param,
536 &ctrlc_stop_flag,
537 orig_file_size.unwrap(),
538 ref_block_pos,
539 &ref_block,
540 hash_ctx.unwrap(),
541 );
542
543 stats.hash_result = Some(hash_result);
544 }
545
546 Ok(Some(stats))
547}