1use std::{
15 fs::File,
16 io::{Cursor, Read, Seek, SeekFrom},
17 ops::RangeInclusive,
18 path::Path,
19};
20
21use range_set::RangeSet;
22use serde::{Deserialize, Serialize};
23use serde_json::Value;
24use sha2::{Digest, Sha256, Sha384, Sha512};
26
27use crate::{crypto::base64::encode, utils::io_utils::stream_len, Error, Result};
28
29const MAX_HASH_BUF: usize = 256 * 1024 * 1024; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
32pub struct HashRange {
34 start: u64,
35 length: u64,
36
37 #[serde(skip)]
38 bmff_offset: Option<u64>, }
40
41impl HashRange {
42 pub fn new(start: u64, length: u64) -> Self {
43 HashRange {
44 start,
45 length,
46 bmff_offset: None,
47 }
48 }
49
50 #[allow(dead_code)]
52 pub fn set_start(&mut self, start: u64) {
53 self.start = start;
54 }
55
56 pub fn start(&self) -> u64 {
58 self.start
59 }
60
61 pub fn length(&self) -> u64 {
63 self.length
64 }
65
66 pub fn set_length(&mut self, length: u64) {
67 self.length = length;
68 }
69
70 pub fn set_bmff_offset(&mut self, offset: u64) {
72 self.bmff_offset = Some(offset);
73 }
74
75 pub fn bmff_offset(&self) -> Option<u64> {
77 self.bmff_offset
78 }
79}
80
81pub fn vec_compare(va: &[u8], vb: &[u8]) -> bool {
83 (va.len() == vb.len()) && va.iter()
85 .zip(vb)
86 .all(|(a,b)| a == b)
87}
88
89#[derive(Clone, Debug)]
90pub enum Hasher {
91 SHA256(Sha256),
92 SHA384(Sha384),
93 SHA512(Sha512),
94}
95
96impl Default for Hasher {
97 fn default() -> Self {
98 Hasher::SHA256(Sha256::new())
99 }
100}
101
102impl Hasher {
103 pub fn update(&mut self, data: &[u8]) {
105 use Hasher::*;
106 match self {
108 SHA256(ref mut d) => d.update(data),
109 SHA384(ref mut d) => d.update(data),
110 SHA512(ref mut d) => d.update(data),
111 }
112 }
113
114 pub fn finalize(hasher_enum: Hasher) -> Vec<u8> {
116 use Hasher::*;
117 match hasher_enum {
119 SHA256(d) => d.finalize().to_vec(),
120 SHA384(d) => d.finalize().to_vec(),
121 SHA512(d) => d.finalize().to_vec(),
122 }
123 }
124
125 pub fn finalize_reset(&mut self) -> Vec<u8> {
126 use Hasher::*;
127
128 match self {
130 SHA256(ref mut d) => d.finalize_reset().to_vec(),
131 SHA384(ref mut d) => d.finalize_reset().to_vec(),
132 SHA512(ref mut d) => d.finalize_reset().to_vec(),
133 }
134 }
135
136 pub fn new(alg: &str) -> Result<Hasher> {
137 match alg {
138 "sha256" => Ok(Hasher::SHA256(Sha256::new())),
139 "sha384" => Ok(Hasher::SHA384(Sha384::new())),
140 "sha512" => Ok(Hasher::SHA512(Sha512::new())),
141 _ => Err(Error::UnsupportedType),
142 }
143 }
144}
145
146pub fn hash_by_alg(alg: &str, data: &[u8], exclusions: Option<Vec<HashRange>>) -> Vec<u8> {
148 let mut reader = Cursor::new(data);
149
150 hash_stream_by_alg(alg, &mut reader, exclusions, true).unwrap_or_default()
151}
152
153pub fn hash_by_alg_with_inclusions(alg: &str, data: &[u8], inclusions: Vec<HashRange>) -> Vec<u8> {
155 let mut reader = Cursor::new(data);
156
157 hash_stream_by_alg(alg, &mut reader, Some(inclusions), false).unwrap_or_default()
158}
159
160pub fn hash_asset_by_alg(
162 alg: &str,
163 asset_path: &Path,
164 exclusions: Option<Vec<HashRange>>,
165) -> Result<Vec<u8>> {
166 let mut file = File::open(asset_path)?;
167 hash_stream_by_alg(alg, &mut file, exclusions, true)
168}
169
170pub fn hash_asset_by_alg_with_inclusions(
172 alg: &str,
173 asset_path: &Path,
174 inclusions: Vec<HashRange>,
175) -> Result<Vec<u8>> {
176 let mut file = File::open(asset_path)?;
177 hash_stream_by_alg(alg, &mut file, Some(inclusions), false)
178}
179
180pub(crate) fn hash_stream_by_alg_with_progress<R>(
223 alg: &str,
224 data: &mut R,
225 hash_range: Option<Vec<HashRange>>,
226 is_exclusion: bool,
227 mut progress: Option<&mut dyn FnMut(u32, u32) -> Result<()>>,
228) -> Result<Vec<u8>>
229where
230 R: Read + Seek + ?Sized,
231{
232 let mut bmff_v2_starts: Vec<u64> = Vec::new();
233
234 use Hasher::*;
235 let mut hasher_enum = match alg {
236 "sha256" => SHA256(Sha256::new()),
237 "sha384" => SHA384(Sha384::new()),
238 "sha512" => SHA512(Sha512::new()),
239 _ => {
240 return Err(Error::UnsupportedType);
241 }
242 };
243
244 let data_len = stream_len(data)?;
245 data.rewind()?;
246
247 if data_len < 1 {
248 return Err(Error::OtherError("no data to hash".into()));
249 }
250
251 let ranges = match hash_range {
252 Some(mut hr) if !hr.is_empty() => {
253 hr.sort_by_key(|a| a.start());
256
257 let num_blocks = hr.len();
259 let range_end = hr[num_blocks - 1].start() + hr[num_blocks - 1].length();
260 let data_end = data_len - 1;
261
262 if data_len < range_end {
264 return Err(Error::BadParam(
265 "The exclusion range exceed the data length".to_string(),
266 ));
267 }
268
269 if is_exclusion {
270 let mut ranges_vec: Vec<RangeInclusive<u64>> = Vec::new();
272 let mut ranges = RangeSet::<[RangeInclusive<u64>; 1]>::from(0..=data_end);
273 for exclusion in hr {
274 if let Some(offset) = exclusion.bmff_offset() {
277 bmff_v2_starts.push(offset);
278 continue;
279 }
280
281 if exclusion.length() == 0 {
282 continue;
283 }
284
285 let end = exclusion
286 .start()
287 .checked_add(exclusion.length())
288 .ok_or(Error::BadParam("No exclusion range".to_string()))?
289 .checked_sub(1)
290 .ok_or(Error::BadParam("No exclusion range".to_string()))?;
291 let exclusion_start = exclusion.start();
292 ranges.remove_range(exclusion_start..=end);
293 }
294
295 if !bmff_v2_starts.is_empty() {
297 bmff_v2_starts.sort();
298
299 for r in ranges.into_smallvec() {
301 let mut current_range = r;
303 for os in &bmff_v2_starts {
304 if current_range.contains(os) {
305 if *current_range.start() == *os {
306 ranges_vec.push(RangeInclusive::new(*os, *os));
307 } else {
309 ranges_vec
310 .push(RangeInclusive::new(*current_range.start(), *os - 1)); ranges_vec.push(RangeInclusive::new(*os, *os)); current_range = RangeInclusive::new(*os, *current_range.end());
313 }
315 }
316 }
317 ranges_vec.push(current_range);
318 }
319
320 let range_start = RangeInclusive::new(0, 0);
322 let range_end = RangeInclusive::new(data_end, data_end);
323 let before_any_range = *ranges_vec.first().unwrap_or(&range_start).start();
324 let after_any_range = *ranges_vec.last().unwrap_or(&range_end).end();
325
326 for os in &bmff_v2_starts {
327 if !ranges_vec.iter().any(|r| r.contains(os))
328 && *os > before_any_range
329 && *os < after_any_range
330 {
331 ranges_vec.push(RangeInclusive::new(*os, *os));
332 }
333 }
334
335 ranges_vec.sort_by(|a, b| {
337 let a_start = a.start();
338 let b_start = b.start();
339 a_start.cmp(b_start)
340 });
341
342 ranges_vec
343 } else {
344 for r in ranges.into_smallvec() {
345 ranges_vec.push(r);
346 }
347 ranges_vec
348 }
349 } else {
350 let mut ranges_vec: Vec<RangeInclusive<u64>> = Vec::new();
352 for inclusion in hr {
353 if inclusion.length() == 0 {
354 continue;
355 }
356
357 let end = inclusion.start() + inclusion.length() - 1;
358 let inclusion_start = inclusion.start();
359
360 if let Some(offset) = inclusion.bmff_offset() {
363 ranges_vec.push(RangeInclusive::new(offset, offset));
364 bmff_v2_starts.push(offset);
365 }
366
367 ranges_vec.push(RangeInclusive::new(inclusion_start, end));
369 }
370 ranges_vec
371 }
372 }
373 _ => {
374 let mut ranges_vec: Vec<RangeInclusive<u64>> = Vec::new();
375 let data_end = data_len - 1;
376 ranges_vec.push(RangeInclusive::new(0_u64, data_end));
377
378 ranges_vec
379 }
380 };
381
382 let total = ranges.len() as u32;
383 let mut step: u32 = 0;
384
385 if cfg!(target_arch = "wasm32") {
386 for r in ranges {
388 step += 1;
389 if let Some(cb) = progress.as_mut() {
390 cb(step, total)?;
391 }
392
393 let start = r.start();
394 let end = r.end();
395 let mut chunk_left = end - start + 1;
396
397 if bmff_v2_starts.contains(start) && end == start {
399 hasher_enum.update(&start.to_be_bytes());
400 continue;
401 }
402
403 data.seek(SeekFrom::Start(*start))?;
405
406 loop {
407 let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
408
409 data.read_exact(&mut chunk)?;
410
411 hasher_enum.update(&chunk);
412
413 chunk_left -= chunk.len() as u64;
414 if chunk_left == 0 {
415 break;
416 }
417 }
418 }
419 } else {
420 for r in ranges {
422 step += 1;
423 if let Some(cb) = progress.as_mut() {
424 cb(step, total)?;
425 }
426
427 let start = r.start();
428 let end = r.end();
429 let mut chunk_left = end - start + 1;
430
431 if bmff_v2_starts.contains(start) && end == start {
433 hasher_enum.update(&start.to_be_bytes());
434 continue;
435 }
436
437 data.seek(SeekFrom::Start(*start))?;
439
440 let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
441 data.read_exact(&mut chunk)?;
442
443 loop {
444 let (tx, rx) = std::sync::mpsc::channel();
445
446 chunk_left -= chunk.len() as u64;
447
448 std::thread::spawn(move || {
449 hasher_enum.update(&chunk);
450 tx.send(hasher_enum).unwrap_or_default();
451 });
452
453 if chunk_left == 0 {
455 hasher_enum = match rx.recv() {
456 Ok(hasher) => hasher,
457 Err(_) => return Err(Error::ThreadReceiveError),
458 };
459 break;
460 }
461
462 let mut next_chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
464 data.read_exact(&mut next_chunk)?;
465
466 hasher_enum = match rx.recv() {
467 Ok(hasher) => hasher,
468 Err(_) => return Err(Error::ThreadReceiveError),
469 };
470
471 chunk = next_chunk;
472 }
473 }
474 }
475
476 Ok(Hasher::finalize(hasher_enum))
478}
479
480pub fn hash_stream_by_alg<R>(
482 alg: &str,
483 data: &mut R,
484 hash_range: Option<Vec<HashRange>>,
485 is_exclusion: bool,
486) -> Result<Vec<u8>>
487where
488 R: Read + Seek + ?Sized,
489{
490 hash_stream_by_alg_with_progress(alg, data, hash_range, is_exclusion, None)
491}
492
493pub fn verify_by_alg(
495 alg: &str,
496 hash: &[u8],
497 data: &[u8],
498 exclusions: Option<Vec<HashRange>>,
499) -> bool {
500 let data_hash = hash_by_alg(alg, data, exclusions);
502 vec_compare(hash, &data_hash)
503}
504
505pub fn verify_asset_by_alg(
507 alg: &str,
508 hash: &[u8],
509 asset_path: &Path,
510 exclusions: Option<Vec<HashRange>>,
511) -> bool {
512 if let Ok(data_hash) = hash_asset_by_alg(alg, asset_path, exclusions) {
514 vec_compare(hash, &data_hash)
515 } else {
516 false
517 }
518}
519
520pub fn verify_stream_by_alg<R>(
521 alg: &str,
522 hash: &[u8],
523 reader: &mut R,
524 hash_range: Option<Vec<HashRange>>,
525 is_exclusion: bool,
526) -> bool
527where
528 R: Read + Seek + ?Sized,
529{
530 if let Ok(data_hash) = hash_stream_by_alg(alg, reader, hash_range, is_exclusion) {
531 vec_compare(hash, &data_hash)
532 } else {
533 false
534 }
535}
536
537pub fn concat_and_hash(alg: &str, left: &[u8], right: Option<&[u8]>) -> Vec<u8> {
539 let mut temp = left.to_vec();
540
541 if let Some(r) = right {
542 temp.append(&mut r.to_vec())
543 }
544
545 hash_by_alg(alg, &temp, None)
546}
547
548pub fn hash_to_b64(mut value: Value) -> Value {
550 use std::collections::VecDeque;
551
552 let mut queue = VecDeque::new();
553 queue.push_back(&mut value);
554
555 while let Some(current) = queue.pop_front() {
556 match current {
557 Value::Object(obj) => {
558 for (_, v) in obj.iter_mut() {
559 if let Value::Array(hash_arr) = v {
560 if !hash_arr.is_empty() && hash_arr.iter().all(|x| x.is_number()) {
561 let mut hash_bytes = Vec::with_capacity(hash_arr.len());
563 for n in hash_arr.iter() {
565 if let Some(num) = n.as_u64() {
566 hash_bytes.push(num as u8);
567 }
568 }
569 *v = Value::String(encode(&hash_bytes));
570 }
571 }
572 queue.push_back(v);
573 }
574 }
575 Value::Array(arr) => {
576 for v in arr.iter_mut() {
577 queue.push_back(v);
578 }
579 }
580 _ => {}
581 }
582 }
583 value
584}
585
586#[cfg(test)]
587mod tests {
588 #![allow(clippy::unwrap_used)]
589
590 use std::io::Cursor;
591
592 use super::*;
593
594 #[test]
595 fn progress_callback_is_called() {
596 let data = vec![0u8; 64];
597 let mut called = false;
598 let mut reader = Cursor::new(&data);
599 let mut cb = |_step, _total| {
600 called = true;
601 Ok(())
602 };
603 hash_stream_by_alg_with_progress("sha256", &mut reader, None, true, Some(&mut cb)).unwrap();
604 assert!(called, "progress callback should have been invoked");
605 }
606
607 #[test]
608 fn progress_callback_can_cancel() {
609 let data = vec![0u8; 64];
610 let mut reader = Cursor::new(&data);
611 let mut cb = |_step, _total| Err(Error::OperationCancelled);
612 let result =
613 hash_stream_by_alg_with_progress("sha256", &mut reader, None, true, Some(&mut cb));
614 assert!(
615 matches!(result, Err(Error::OperationCancelled)),
616 "expected OperationCancelled, got {result:?}"
617 );
618 }
619}