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