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