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