blkar_lib/sbx_block/
metadata.rs

1use super::Error;
2use crate::misc_utils;
3use crate::multihash;
4use crate::sbx_specs::{ver_to_data_size, Version};
5use crate::time_utils;
6use std;
7use std::fmt;
8
9#[derive(Clone, Debug, PartialEq)]
10pub enum Metadata {
11    FNM(String),
12    SNM(String),
13    FSZ(u64),
14    FDT(i64),
15    SDT(i64),
16    HSH(multihash::HashBytes),
17    RSD(u8),
18    RSP(u8),
19}
20
21impl fmt::Display for Metadata {
22    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23        use Metadata::*;
24
25        match self {
26            FNM(s) => write!(f, "{}", s),
27            SNM(s) => write!(f, "{}", s),
28            FSZ(x) => write!(f, "{}", *x),
29            FDT(x) | SDT(x) => {
30                match (
31                    time_utils::i64_secs_to_date_time_string(*x, time_utils::TimeMode::UTC),
32                    time_utils::i64_secs_to_date_time_string(*x, time_utils::TimeMode::Local),
33                ) {
34                    (Some(u), Some(l)) => write!(f, "{} (UTC)  {} (Local)", u, l),
35                    _ => write!(f, "Invalid recorded date time"),
36                }
37            }
38            HSH(h) => write!(
39                f,
40                "{} - {}",
41                multihash::hash_type_to_string(h.0),
42                misc_utils::bytes_to_lower_hex_string(&h.1)
43            ),
44            RSD(x) => write!(f, "{}", *x),
45            RSP(x) => write!(f, "{}", *x),
46        }
47    }
48}
49
50pub enum UncheckedMetadata {
51    FNM(Vec<u8>),
52    SNM(Vec<u8>),
53    FSZ(u64),
54    FDT(i64),
55    SDT(i64),
56    HSH(multihash::HashBytes),
57    RSD(u8),
58    RSP(u8),
59}
60
61#[derive(Clone, Copy, Debug, PartialEq)]
62pub enum MetadataID {
63    FNM,
64    SNM,
65    FSZ,
66    FDT,
67    SDT,
68    HSH,
69    RSD,
70    RSP,
71}
72
73static PREAMBLE_LEN: usize = 3 + 1;
74
75fn single_info_size(meta: &Metadata) -> usize {
76    use self::Metadata::*;
77    use std::mem;
78    match *meta {
79        FNM(ref x) | SNM(ref x) => x.len(),
80        FSZ(_) | FDT(_) | SDT(_) => mem::size_of::<u64>(),
81        HSH(ref x) => multihash::specs::Param::new(x.0).total_length(),
82        RSD(_) | RSP(_) => mem::size_of::<u8>(),
83    }
84}
85
86fn single_meta_size(meta: &Metadata) -> usize {
87    PREAMBLE_LEN + single_info_size(meta)
88}
89
90pub fn id_to_bytes(id: MetadataID) -> [u8; 3] {
91    use self::MetadataID::*;
92    match id {
93        FNM => [b'F', b'N', b'M'],
94        SNM => [b'S', b'N', b'M'],
95        FSZ => [b'F', b'S', b'Z'],
96        FDT => [b'F', b'D', b'T'],
97        SDT => [b'S', b'D', b'T'],
98        HSH => [b'H', b'S', b'H'],
99        RSD => [b'R', b'S', b'D'],
100        RSP => [b'R', b'S', b'P'],
101    }
102}
103
104pub fn id_to_str(id: MetadataID) -> &'static str {
105    use self::MetadataID::*;
106    match id {
107        FNM => "FNM",
108        SNM => "SNM",
109        FSZ => "FSZ",
110        FDT => "FDT",
111        SDT => "SDT",
112        HSH => "HSH",
113        RSD => "RSD",
114        RSP => "RSP",
115    }
116}
117
118pub fn meta_to_id(meta: &Metadata) -> MetadataID {
119    match *meta {
120        Metadata::FNM(_) => MetadataID::FNM,
121        Metadata::SNM(_) => MetadataID::SNM,
122        Metadata::FSZ(_) => MetadataID::FSZ,
123        Metadata::FDT(_) => MetadataID::FDT,
124        Metadata::SDT(_) => MetadataID::SDT,
125        Metadata::HSH(_) => MetadataID::HSH,
126        Metadata::RSD(_) => MetadataID::RSD,
127        Metadata::RSP(_) => MetadataID::RSP,
128    }
129}
130
131fn single_to_bytes(meta: &Metadata, buffer: &mut [u8]) -> Result<usize, ()> {
132    let total_size = single_meta_size(meta);
133    let info_size = single_info_size(meta);
134
135    if buffer.len() < total_size {
136        return Err(());
137    }
138
139    use self::Metadata::*;
140
141    // write id
142    let id = id_to_bytes(meta_to_id(meta));
143    for i in 0..id.len() {
144        buffer[i] = id[i];
145    }
146
147    // write length
148    buffer[3] = info_size as u8;
149
150    let dst = &mut buffer[PREAMBLE_LEN..PREAMBLE_LEN + info_size];
151
152    // write info
153    match *meta {
154        FNM(ref x) | SNM(ref x) => {
155            dst.copy_from_slice(x.as_bytes());
156        }
157        FSZ(x) => {
158            let be_bytes: [u8; 8] = unsafe { std::mem::transmute::<u64, [u8; 8]>(x.to_be()) };
159            dst.copy_from_slice(&be_bytes);
160        }
161        FDT(x) | SDT(x) => {
162            let be_bytes: [u8; 8] = unsafe { std::mem::transmute::<i64, [u8; 8]>(x.to_be()) };
163            dst.copy_from_slice(&be_bytes);
164        }
165        HSH(ref x) => {
166            multihash::hash_bytes_to_bytes(x, dst);
167        }
168        RSD(x) | RSP(x) => {
169            dst[0] = x;
170        }
171    }
172
173    Ok(total_size)
174}
175
176pub fn to_bytes(meta: &[Metadata], buffer: &mut [u8]) -> Result<(), Error> {
177    let mut cur_pos = 0;
178    for m in meta.iter() {
179        let size_written = match single_to_bytes(m, &mut buffer[cur_pos..]) {
180            Ok(x) => x,
181            Err(()) => {
182                return Err(Error::TooMuchMetadata(meta.to_vec()));
183            }
184        };
185
186        cur_pos += size_written;
187    }
188
189    // fill the rest with padding 0x1A
190    for i in cur_pos..buffer.len() {
191        buffer[i] = 0x1A;
192    }
193
194    Ok(())
195}
196
197pub fn make_too_much_meta_err_string(version: Version, meta: &[Metadata]) -> String {
198    let msg = make_distribution_string(version, meta);
199
200    format!("Too much metadata, distribution :\n{}", &msg)
201}
202
203pub fn make_distribution_string(version: Version, metas: &[Metadata]) -> String {
204    let mut string = String::with_capacity(1000);
205    string.push_str("|  ID | Info length | Total length |\n");
206
207    let mut overall_total = 0;
208    let max_size = ver_to_data_size(version);
209
210    for i in 0..metas.len() {
211        let id_str = id_to_str(meta_to_id(&metas[i]));
212        let total_size = single_meta_size(&metas[i]);
213        let info_size = single_info_size(&metas[i]);
214
215        overall_total += total_size;
216
217        string.push_str(&format!(
218            "| {} |      {:6} |       {:6} |\n",
219            id_str, info_size, total_size
220        ));
221    }
222    string.push_str("\n");
223    string.push_str(&format!("Overall total length : {:6}\n", overall_total));
224    string.push_str(&format!("Maximum total length : {:6}", max_size));
225    string
226}
227
228mod parsers {
229    use super::super::super::misc_utils;
230    use super::super::super::multihash::parsers::multihash_w_len_p;
231    use super::UncheckedMetadata;
232    use super::UncheckedMetadata::*;
233
234    use nom::number::complete::be_i64;
235    use nom::number::complete::be_u64;
236    use nom::number::complete::be_u8;
237
238    macro_rules! make_meta_parser {
239        (
240            $name:ident, $id:expr, $constructor:path
241                => num, $n_must_be:expr, $res_parser:ident
242        ) => {
243            named!(
244                $name<UncheckedMetadata>,
245                do_parse!(
246                    _id: tag!($id)
247                        >> n: be_u8
248                        >> res: map_opt!(cond!(n >= 1 && n == $n_must_be, $res_parser), move |x| x)
249                        >> ($constructor(res))
250                )
251            );
252        };
253        (
254            $name:ident, $id:expr, $constructor:path => str
255        ) => {
256            named!(
257                $name<UncheckedMetadata>,
258                do_parse!(
259                    tag!($id)
260                        >> n: be_u8
261                        >> res: map_opt!(cond!(n >= 1, take!(n)), move |x| x)
262                        >> ($constructor(misc_utils::slice_to_vec(res)))
263                )
264            );
265        };
266    }
267
268    make_meta_parser!(fnm_p, b"FNM", FNM => str);
269    make_meta_parser!(snm_p, b"SNM", SNM => str);
270    make_meta_parser!(fsz_p, b"FSZ", FSZ => num, 8, be_u64);
271    make_meta_parser!(fdt_p, b"FDT", FDT => num, 8, be_i64);
272    make_meta_parser!(sdt_p, b"SDT", SDT => num, 8, be_i64);
273    make_meta_parser!(rsd_p, b"RSD", RSD => num, 1, be_u8);
274    make_meta_parser!(rsp_p, b"RSP", RSP => num, 1, be_u8);
275
276    named!(
277        hsh_p<UncheckedMetadata>,
278        do_parse!(_id: tag!(b"HSH") >> res: multihash_w_len_p >> (HSH(res)))
279    );
280
281    named!(pub meta_p <Vec<UncheckedMetadata>>,
282           many0!(
283               alt!(
284                   complete!(fnm_p)
285                       | complete!(snm_p)
286                       | complete!(fsz_p)
287                       | complete!(fdt_p)
288                       | complete!(sdt_p)
289                       | complete!(hsh_p)
290                       | complete!(rsd_p)
291                       | complete!(rsp_p)
292               )
293           )
294    );
295}
296
297pub fn filter_invalid_metadata(input: Vec<UncheckedMetadata>) -> Vec<Metadata> {
298    use self::UncheckedMetadata::*;
299    let mut res = Vec::with_capacity(input.len());
300
301    let mut rsd: Option<usize> = None;
302    let mut rsp: Option<usize> = None;
303
304    for meta in input.into_iter() {
305        let possibly_push: Option<Metadata> = match meta {
306            FNM(x) => match String::from_utf8(x) {
307                Ok(x) => Some(Metadata::FNM(x)),
308                Err(_) => None,
309            },
310            SNM(x) => match String::from_utf8(x) {
311                Ok(x) => Some(Metadata::SNM(x)),
312                Err(_) => None,
313            },
314            FSZ(x) => Some(Metadata::FSZ(x)),
315            FDT(x) => Some(Metadata::FDT(x)),
316            SDT(x) => Some(Metadata::SDT(x)),
317            HSH(h) => Some(Metadata::HSH(h)),
318            RSD(d) => {
319                if 1 <= d {
320                    // only record first occurance
321                    if let None = rsd {
322                        rsd = Some(d as usize);
323                    }
324                    Some(Metadata::RSD(d))
325                } else {
326                    None
327                }
328            }
329            RSP(p) => {
330                if 1 <= p {
331                    // only record first occurance
332                    if let None = rsp {
333                        rsp = Some(p as usize);
334                    }
335                    Some(Metadata::RSP(p))
336                } else {
337                    None
338                }
339            }
340        };
341
342        match possibly_push {
343            None => {}
344            Some(x) => res.push(x),
345        }
346    }
347
348    let res = match (rsd, rsp) {
349        (Some(d), Some(p)) if d + p > 256 => {
350            // remove all RSD and RSP fields
351            res.into_iter()
352                .filter(|x| meta_to_id(x) != MetadataID::RSD && meta_to_id(x) != MetadataID::RSP)
353                .collect()
354        }
355        (..) => res,
356    };
357
358    res
359}
360
361pub fn from_bytes(bytes: &[u8]) -> Result<Vec<Metadata>, Error> {
362    match parsers::meta_p(bytes) {
363        Ok((_, res)) => Ok(filter_invalid_metadata(res)),
364        _ => Err(Error::ParseError),
365    }
366}
367
368pub fn get_meta_ref_by_id(metas: &[Metadata], id: MetadataID) -> Option<&Metadata> {
369    for m in metas.iter() {
370        if meta_to_id(m) == id {
371            return Some(m);
372        }
373    }
374    None
375}
376
377pub fn get_meta_ref_mut_by_id(metas: &mut [Metadata], id: MetadataID) -> Option<&mut Metadata> {
378    for m in metas.iter_mut() {
379        if meta_to_id(m) == id {
380            return Some(m);
381        }
382    }
383
384    None
385}