1use crate::bmap::{BmapBuilder, BmapBuilderError, HashValue};
2use quick_xml::de::{DeError, from_str};
3use serde::Deserialize;
4use thiserror::Error;
5
6#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
7#[serde(rename_all = "lowercase")]
8#[non_exhaustive]
9pub enum HashType {
10 Sha256,
11}
12
13#[derive(Debug, Deserialize)]
14struct Range {
15 #[serde(rename = "@chksum")]
16 chksum: String,
17 #[serde(rename = "$value")]
18 range: String,
19}
20
21#[derive(Debug, Deserialize)]
22struct BlockMap {
23 #[serde(rename = "Range")]
24 ranges: Vec<Range>,
25}
26
27#[allow(dead_code)]
28#[derive(Debug, Deserialize)]
29struct Bmap {
30 #[serde(rename = "@version")]
31 version: String,
32 #[serde(rename = "ImageSize")]
33 image_size: u64,
34 #[serde(rename = "BlockSize")]
35 block_size: u64,
36 #[serde(rename = "BlocksCount")]
37 blocks_count: u64,
38 #[serde(rename = "MappedBlocksCount")]
39 mapped_blocks_count: u64,
40 #[serde(rename = "ChecksumType")]
41 checksum_type: HashType,
42 #[serde(rename = "BmapFileChecksum")]
43 bmap_file_checksum: String,
44 #[serde(rename = "BlockMap")]
45 block_map: BlockMap,
46}
47
48#[derive(Debug, Error)]
49pub enum XmlError {
50 #[error("Failed to parse bmap XML: {0}")]
51 XmlParsError(#[from] DeError),
52 #[error("Invalid bmap file: {0}")]
53 InvalidFIleError(#[from] BmapBuilderError),
54 #[error("Unknown checksum type: {0}")]
55 UnknownChecksumType(String),
56 #[error("Invalid checksum: {0}")]
57 InvalidChecksum(String),
58}
59
60const fn hexdigit_to_u8(c: u8) -> Option<u8> {
61 match c {
62 b'a'..=b'f' => Some(c - b'a' + 0xa),
63 b'A'..=b'F' => Some(c - b'A' + 0xa),
64 b'0'..=b'9' => Some(c - b'0'),
65 _ => None,
66 }
67}
68
69fn str_to_digest(s: String, digest: &mut [u8]) -> Result<(), XmlError> {
70 let l = digest.len();
71 if s.len() != l * 2 {
72 return Err(XmlError::InvalidChecksum(format!(
73 "No enough chars: {} {}",
74 s,
75 s.len()
76 )));
77 }
78
79 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
80 let hi = match hexdigit_to_u8(chunk[0]) {
81 Some(v) => v,
82 None => return Err(XmlError::InvalidChecksum(s)),
83 };
84 let lo = match hexdigit_to_u8(chunk[1]) {
85 Some(v) => v,
86 None => return Err(XmlError::InvalidChecksum(s)),
87 };
88 digest[i] = hi << 4 | lo;
89 }
90
91 Ok(())
92}
93
94pub(crate) fn from_xml(xml: &str) -> Result<crate::bmap::Bmap, XmlError> {
95 let b: Bmap = from_str(xml)?;
96 let mut builder = BmapBuilder::default();
97 let hash_type = b.checksum_type;
98 builder
99 .image_size(b.image_size)
100 .block_size(b.block_size)
101 .blocks(b.blocks_count)
102 .checksum_type(hash_type)
103 .mapped_blocks(b.mapped_blocks_count);
104
105 for range in b.block_map.ranges {
106 let mut split = range.range.trim().splitn(2, '-');
107 let start = match split.next() {
108 Some(s) => s.parse().unwrap(),
109 None => unimplemented!("woops"),
110 };
111 let end = match split.next() {
112 Some(s) => s.parse().unwrap(),
113 None => start,
114 };
115
116 let checksum = match hash_type {
117 HashType::Sha256 => {
118 let mut v = [0; 32];
119 str_to_digest(range.chksum, &mut v)?;
120 HashValue::Sha256(v)
121 }
122 };
123 builder.add_block_range(start, end, checksum);
124 }
125
126 builder.build().map_err(std::convert::Into::into)
127}