Skip to main content

a2kit/img/
meta.rs

1//! # Disk Image Metadata Handling
2//! 
3//! Some disk images wrap the track data in a metadata structure.
4//! This module contains machinery for exposing this metadata to the user.
5//! One specific goal is to provide an object representing the metadata that
6//! can be passed through the CLI pipeline, e.g., we may want to get some
7//! subset of the metadata from one image and put it in another.
8//! 
9//! The main structural concept is that of a tree.  The tree is exposed
10//! through the CLI as a JSON string.  A specific value is referenced using the
11//! native JSON, e.g., `{"woz2":{"info":{"sides":"01"}}}`, while a location is
12//! referenced as a list of `&str`, e.g., `["woz2","info","sides"]`.
13//! A path notation is supported for user interaction, e.g., `/woz2/info/sides`.
14//!
15//! Binary is encoded as hex strings, however, there is an option to break a
16//! value out into `_raw` and `_pretty` values, where the `_pretty` value can
17//! be put as decimal, given units, etc..
18//! 
19//! This module exports several `macro_rules` to streamline metadata handling code.
20//! These macros will take an arbitrary identifier chain, such as `self.info.sides`,
21//! and resolve it into the appropriate path or JSON fragment.
22
23use log::error;
24use crate::img::{Error,DiskImageType};
25use crate::STDRESULT;
26
27/// Get a byte value from the image into a JSON object as a hex string.
28/// ```rs
29/// getByte!(root:JsonValue,image_type:String,self.path.to.byte:u8)
30/// ```
31#[macro_export]
32macro_rules! getByte {
33    ($root:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
34        $root[&$typ]$([stringify!($x)])+ = json::JsonValue::String(hex::ToHex::encode_hex(&vec![$slf.$($x).+]))
35    };
36}
37
38/// Get a byte value from the image into a JSON object, adding the `_raw` terminus.
39/// The `_pretty` terminus has to be added by hand.
40/// ```rs
41/// getByteEx!(root:JsonValue,image_type:String,self.path.to.byte:u8)
42/// ```
43#[macro_export]
44macro_rules! getByteEx {
45    ($root:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
46        $root[&$typ]$([stringify!($x)])+ = json::JsonValue::new_object();
47        $root[&$typ]$([stringify!($x)])+["_raw"] = json::JsonValue::String(hex::ToHex::encode_hex(&vec![$slf.$($x).+]))
48    };
49}
50
51/// get a multi-byte value from the image into a JSON object as hex string
52/// ```rs
53/// getHex!(root:JsonValue,image_type:String,self.path.to.bytes:[u8])
54/// ```
55#[macro_export]
56macro_rules! getHex {
57    ($root:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
58        $root[&$typ]$([stringify!($x)])+ = json::JsonValue::String(hex::ToHex::encode_hex(&$slf.$($x).+))
59    };
60}
61
62/// Get a multi-byte value from the image into a JSON object, adding the `_raw` terminus.
63/// The `_pretty` terminus has to be added by hand.
64/// ```rs
65/// getHexEx!(root:JsonValue,image_type:String,self.path.to.bytes:[u8])
66/// ```
67#[macro_export]
68macro_rules! getHexEx {
69    ($root:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
70        $root[&$typ]$([stringify!($x)])+ = json::JsonValue::new_object();
71        $root[&$typ]$([stringify!($x)])+["_raw"] = json::JsonValue::String(hex::ToHex::encode_hex(&$slf.$($x).+))
72    };
73}
74
75/// Parse a hex string containing one byte and put value into the image using the given key path.
76/// ```rs
77/// putByte!(val:&str,key_path:&Vec<String>,image_type:String,self.path.to.byte:u8)
78/// ```
79#[macro_export]
80macro_rules! putByte {
81    ($val:ident,$key:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
82        if meta::match_key($key,&[&$typ,$(stringify!($x)),+]) {
83            return meta::set_metadata_byte($val, &mut $slf.$($x).+);
84        }
85    };
86}
87
88/// Parse a hex string containing multiple bytes and put value into the image using the given key path.
89/// ```rs
90/// putHex!(val:&str,key_path:&Vec<String>,image_type:String,self.path.to.bytes:[u8])
91/// ```
92#[macro_export]
93macro_rules! putHex {
94    ($val:ident,$key:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
95        if meta::match_key($key,&[&$typ,$(stringify!($x)),+]) {
96            return meta::set_metadata_hex($val, &mut $slf.$($x).+);
97        }
98    };
99}
100
101/// Put a variable length UTF8 string into the image using the given key path.
102/// ```rs
103/// putString!(val:&str,key_path:&Vec<String>,image_type:String,self.path.to.string:String)
104/// ```
105#[macro_export]
106macro_rules! putString {
107    ($val:ident,$key:ident,$typ:ident,$slf:ident.$($x:ident).+) => {
108        if meta::match_key($key,&[&$typ,$(stringify!($x)),+]) {
109            $slf.$($x).+ = $val.to_string();
110            return Ok(());
111        }
112    };
113}
114
115/// Put a fixed length UTF8 string into the image using the given key path.
116/// ```rs
117/// putString!(val:&str,key_path:&Vec<String>,image_type:String,self.path.to.buf:[u8],pad:u8)
118/// ```
119#[macro_export]
120macro_rules! putStringBuf {
121    ($val:ident,$key:ident,$typ:ident,$slf:ident.$($x:ident).+,$pad:expr_2021) => {
122        if meta::match_key($key,&[&$typ,$(stringify!($x)),+]) {
123            return meta::set_metadata_utf8($val,&mut $slf.$($x).+,$pad);
124        }
125    };
126}
127
128/// Test a key against a slice of &str.  The `test_path` does not need to
129/// and should not include the optional `_raw` key.
130pub fn match_key(key_path: &[String],test_path: &[&str]) -> bool {
131    let pad = match key_path.last() {
132        Some(last) if last=="_raw" => 1,
133        _ => 0
134    };
135    if key_path.len()!=test_path.len()+pad {
136        return false;
137    }
138    for i in 0..test_path.len() {
139        if key_path[i]!=test_path[i] {
140            return false;
141        }
142    }
143    true
144}
145
146/// Test the key for match to the image type.  This relies on the protocol that
147/// all metadata has a root key corresponding to the string representation of
148/// the `DiskImageType`, e.g., every WOZ v1 key starts with `woz1`.
149pub fn test_metadata(key_path: &[String], typ: DiskImageType) -> STDRESULT {
150    let mut node = key_path.iter();
151    match node.next() {
152        Some(key) if key==&typ.to_string() => Ok(()),
153        _ => {
154            error!("metadata root did not match `{}`",typ.to_string());
155            Err(Box::new(Error::MetadataMismatch))
156        }
157    }
158}
159
160/// Set a binary metadata value using a hex string
161pub fn set_metadata_hex(hex_val: &str, buf: &mut [u8]) -> STDRESULT {
162    match hex::decode_to_slice(hex_val, buf) {
163        Ok(()) => Ok(()),
164        Err(e) => Err(Box::new(e))
165    }
166}
167
168/// Set a byte metadata value using a hex string
169pub fn set_metadata_byte(hex_val: &str, buf: &mut u8) -> STDRESULT {
170    let mut slice: [u8;1] = [0];
171    match hex::decode_to_slice(hex_val, &mut slice) {
172        Ok(()) => { *buf = slice[0]; Ok(()) },
173        Err(e) => Err(Box::new(e))
174    }
175}
176
177/// Fill a fixed length metadata buffer with a UTF8 string.
178/// Pad with `pad` when `buf` is longer than `utf8_val`.
179/// Return error if `buf` cannot hold the string.
180pub fn set_metadata_utf8(utf8_val: &str, buf: &mut [u8], pad: u8) -> STDRESULT {
181    let bytes = utf8_val.as_bytes();
182    if bytes.len()<=buf.len() {
183        for i in 0..bytes.len() {
184            buf[i] = bytes[i];
185        }
186        for i in bytes.len()..buf.len() {
187            buf[i] = pad;
188        }
189        Ok(())
190    } else {
191        Err(Box::new(Error::MetadataMismatch))
192    }
193}
194