blkar_lib/
check_core.rs

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 = &param.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        &param.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    // seek to calculated position
358    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                // only report error if the buffer is not completely blank
389                // unless report blank is true
390                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        &param.json_printer,
447        param.pr_verbosity_level,
448        data_par_burst,
449        ctrlc_stop_flag,
450        &param.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, &param.json_printer, ctrlc_stop_flag);
461
462    let file_size = file_utils::get_file_size(&param.in_file)?;
463
464    // calulate length to read and position to seek to
465    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, &param.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}