Skip to content

Commit 4201219

Browse files
anpCQ Bot
authored andcommitted
[configc] Generate Rust functions to parse config from VMOs.
This makes it possible for runners written in Rust (like Starnix) to parse structured config on behalf of the programs they run. Change-Id: I191c3a8a306f452df8992facb221be8ca31ebf8e Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1235172 Fuchsia-Auto-Submit: Adam Perry <[email protected]> Commit-Queue: Auto-Submit <[email protected]> Reviewed-by: Claire Gonyeo <[email protected]>
1 parent 326b7dc commit 4201219

File tree

3 files changed

+325
-67
lines changed

3 files changed

+325
-67
lines changed

tools/configc/goldens/config.rs.golden

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ use fidl::unpersist;
22
use fidl_my_config_lib::Config as FidlConfig;
33
use fuchsia_inspect::{ArithmeticArrayProperty, ArrayProperty, Node};
44
use fuchsia_runtime::{take_startup_handle, HandleInfo, HandleType};
5+
use std::convert::TryInto;
6+
const EXPECTED_CHECKSUM: &[u8] = &[
7+
0xcd, 0x57, 0xb2, 0xa2, 0x89, 0xbb, 0xb6, 0x11, 0xcf, 0x81, 0x50, 0xec, 0x06, 0xc5, 0x06, 0x4c,
8+
0x7c, 0xae, 0x79, 0x0f, 0xaa, 0x73, 0x0b, 0x6f, 0xa1, 0x02, 0xc3, 0x53, 0x7b, 0x94, 0xee, 0x1a,
9+
];
510
#[derive(Debug)]
611
pub struct Config {
712
pub my_flag: bool,
@@ -26,31 +31,36 @@ pub struct Config {
2631
pub my_vector_of_uint8: Vec<u8>,
2732
}
2833
impl Config {
34+
#[doc = r" Take the config startup handle and parse its contents."]
35+
#[doc = r""]
36+
#[doc = r" # Panics"]
37+
#[doc = r""]
38+
#[doc = r" If the config startup handle was already taken or if it is not valid."]
2939
pub fn take_from_startup_handle() -> Self {
40+
let handle_info = HandleInfo::new(HandleType::ComponentConfigVmo, 0);
3041
let config_vmo: zx::Vmo =
31-
take_startup_handle(HandleInfo::new(HandleType::ComponentConfigVmo, 0))
32-
.expect("Config VMO handle must be provided and cannot already have been taken.")
33-
.into();
34-
let config_size =
35-
config_vmo.get_content_size().expect("must be able to read config vmo content size");
36-
assert_ne!(config_size, 0, "config vmo must be non-empty");
37-
let config_bytes =
38-
config_vmo.read_to_vec(0, config_size).expect("must be able to read config vmo");
39-
let checksum_length = u16::from_le_bytes([config_bytes[0], config_bytes[1]]) as usize;
40-
let fidl_start = 2 + checksum_length;
41-
let observed_checksum = &config_bytes[2..fidl_start];
42-
let expected_checksum = vec![
43-
0xcd, 0x57, 0xb2, 0xa2, 0x89, 0xbb, 0xb6, 0x11, 0xcf, 0x81, 0x50, 0xec, 0x06, 0xc5,
44-
0x06, 0x4c, 0x7c, 0xae, 0x79, 0x0f, 0xaa, 0x73, 0x0b, 0x6f, 0xa1, 0x02, 0xc3, 0x53,
45-
0x7b, 0x94, 0xee, 0x1a,
46-
];
47-
assert_eq!(
48-
observed_checksum, expected_checksum,
49-
"checksum from config VMO does not match expected checksum"
50-
);
51-
let fidl_config: FidlConfig = unpersist(&config_bytes[fidl_start..])
52-
.expect("must be able to parse bytes as config FIDL");
53-
Self {
42+
take_startup_handle(handle_info).expect("Config VMO handle must be present.").into();
43+
Self::from_vmo(&config_vmo).expect("Config VMO handle must be valid.")
44+
}
45+
#[doc = r" Parse `Self` from `vmo`."]
46+
pub fn from_vmo(vmo: &zx::Vmo) -> Result<Self, Error> {
47+
let config_size = vmo.get_content_size().map_err(Error::GettingContentSize)?;
48+
let config_bytes = vmo.read_to_vec(0, config_size).map_err(Error::ReadingConfigBytes)?;
49+
Self::from_bytes(&config_bytes)
50+
}
51+
#[doc = r" Parse `Self` from `bytes`."]
52+
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
53+
let (checksum_len_bytes, bytes) = bytes.split_at_checked(2).ok_or(Error::TooFewBytes)?;
54+
let checksum_len_bytes: [u8; 2] =
55+
checksum_len_bytes.try_into().expect("previous call guaranteed 2 element slice");
56+
let checksum_length = u16::from_le_bytes(checksum_len_bytes) as usize;
57+
let (observed_checksum, bytes) =
58+
bytes.split_at_checked(checksum_length).ok_or(Error::TooFewBytes)?;
59+
if observed_checksum != EXPECTED_CHECKSUM {
60+
return Err(Error::ChecksumMismatch { observed_checksum: observed_checksum.to_vec() });
61+
}
62+
let fidl_config: FidlConfig = unpersist(bytes).map_err(Error::Unpersist)?;
63+
Ok(Self {
5464
my_flag: fidl_config.my_flag,
5565
my_int16: fidl_config.my_int16,
5666
my_int32: fidl_config.my_int32,
@@ -71,7 +81,7 @@ impl Config {
7181
my_vector_of_uint32: fidl_config.my_vector_of_uint32,
7282
my_vector_of_uint64: fidl_config.my_vector_of_uint64,
7383
my_vector_of_uint8: fidl_config.my_vector_of_uint8,
74-
}
84+
})
7585
}
7686
pub fn record_inspect(&self, inspector_node: &Node) {
7787
inspector_node.record_bool("my_flag", self.my_flag);
@@ -146,3 +156,63 @@ impl Config {
146156
inspector_node.record(arr);
147157
}
148158
}
159+
#[derive(Debug)]
160+
pub enum Error {
161+
#[doc = r" Failed to read the content size of the VMO."]
162+
GettingContentSize(zx::Status),
163+
#[doc = r" Failed to read the content of the VMO."]
164+
ReadingConfigBytes(zx::Status),
165+
#[doc = r" The VMO was too small for this config library."]
166+
TooFewBytes,
167+
#[doc = r" The VMO's config ABI checksum did not match this library's."]
168+
ChecksumMismatch { observed_checksum: Vec<u8> },
169+
#[doc = r" Failed to parse the non-checksum bytes of the VMO as this library's FIDL type."]
170+
Unpersist(fidl::Error),
171+
}
172+
impl std::fmt::Display for Error {
173+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174+
match self {
175+
Self::GettingContentSize(status) => {
176+
write!(f, "Failed to get content size: {status}")
177+
}
178+
Self::ReadingConfigBytes(status) => {
179+
write!(f, "Failed to read VMO content: {status}")
180+
}
181+
Self::TooFewBytes => {
182+
write!(f, "VMO content is not large enough for this config library.")
183+
}
184+
Self::ChecksumMismatch { observed_checksum } => {
185+
write!(
186+
f,
187+
"ABI checksum mismatch, expected {:?}, got {:?}",
188+
EXPECTED_CHECKSUM, observed_checksum,
189+
)
190+
}
191+
Self::Unpersist(fidl_error) => {
192+
write!(f, "Failed to parse contents of config VMO: {fidl_error}")
193+
}
194+
}
195+
}
196+
}
197+
impl std::error::Error for Error {
198+
#[allow(unused_parens, reason = "rustfmt errors without parens here")]
199+
fn source(&self) -> Option<(&'_ (dyn std::error::Error + 'static))> {
200+
match self {
201+
Self::GettingContentSize(ref status) | Self::ReadingConfigBytes(ref status) => {
202+
Some(status)
203+
}
204+
Self::TooFewBytes => None,
205+
Self::ChecksumMismatch { .. } => None,
206+
Self::Unpersist(ref fidl_error) => Some(fidl_error),
207+
}
208+
}
209+
fn description(&self) -> &str {
210+
match self {
211+
Self::GettingContentSize(_) => "getting content size",
212+
Self::ReadingConfigBytes(_) => "reading VMO contents",
213+
Self::TooFewBytes => "VMO contents too small",
214+
Self::ChecksumMismatch { .. } => "ABI checksum mismatch",
215+
Self::Unpersist(_) => "FIDL parsing error",
216+
}
217+
}
218+
}

tools/lib/config_client/src/lib.rs

Lines changed: 131 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ mod tests {
154154
])
155155
}
156156

157+
/// This test makes sure we sensibly handle config schemas that would normally be banned by cmc.
158+
/// It's like a golden test but the golden infrastructure relies on being able to use real
159+
/// component manifests so this operates directly on TokenStreams.
157160
#[test]
158161
fn bad_field_names() {
159162
let decl = config_decl! {
@@ -197,6 +200,16 @@ type Config = struct {
197200
use fidl::unpersist;
198201
use fuchsia_inspect::{Node};
199202
use fuchsia_runtime::{take_startup_handle, HandleInfo, HandleType};
203+
use std::convert::TryInto;
204+
205+
// This is generated from the config schema for the component. Component Manager also
206+
// computes this in parallel to allow config libraries that its config VMO's ABI matches
207+
// expectations.
208+
const EXPECTED_CHECKSUM: &[u8] = &[
209+
0xb5, 0xf9, 0x33, 0xe8, 0x94, 0x56, 0x3a, 0xf9, 0x61, 0x39, 0xe5, 0x05, 0x79, 0x4b,
210+
0x88, 0xa5, 0x3e, 0xd4, 0xd1, 0x5c, 0x32, 0xe2, 0xb4, 0x49, 0x9e, 0x42, 0xeb, 0xa3,
211+
0x32, 0xb1, 0xf5, 0xbb
212+
];
200213

201214
#[derive(Debug)]
202215
pub struct Config {
@@ -213,55 +226,144 @@ type Config = struct {
213226
}
214227

215228
impl Config {
229+
/// Take the config startup handle and parse its contents.
230+
///
231+
/// # Panics
232+
///
233+
/// If the config startup handle was already taken or if it is not valid.
216234
pub fn take_from_startup_handle() -> Self {
217-
let config_vmo: zx::Vmo = take_startup_handle(HandleInfo::new(HandleType::ComponentConfigVmo, 0))
218-
.expect("Config VMO handle must be provided and cannot already have been taken.")
235+
let handle_info = HandleInfo::new(HandleType::ComponentConfigVmo, 0);
236+
let config_vmo: zx::Vmo = take_startup_handle(handle_info)
237+
.expect("Config VMO handle must be present.")
219238
.into();
220-
let config_size = config_vmo.get_content_size().expect("must be able to read config vmo content size");
221-
assert_ne!(config_size, 0, "config vmo must be non-empty");
222-
223-
let config_bytes = config_vmo.read_to_vec(0, config_size).expect("must be able to read config vmo");
224-
225-
let checksum_length = u16::from_le_bytes([config_bytes[0], config_bytes[1]]) as usize;
226-
let fidl_start = 2 + checksum_length;
227-
let observed_checksum = &config_bytes[2..fidl_start];
228-
let expected_checksum = vec![
229-
0xb5, 0xf9, 0x33, 0xe8, 0x94, 0x56, 0x3a, 0xf9, 0x61, 0x39, 0xe5, 0x05, 0x79,
230-
0x4b, 0x88, 0xa5, 0x3e, 0xd4, 0xd1, 0x5c, 0x32, 0xe2, 0xb4, 0x49, 0x9e, 0x42,
231-
0xeb, 0xa3, 0x32, 0xb1, 0xf5, 0xbb
232-
];
233-
234-
assert_eq!(observed_checksum, expected_checksum, "checksum from config VMO does not match expected checksum");
239+
Self::from_vmo(&config_vmo).expect("Config VMO handle must be valid.")
240+
}
235241

236-
let fidl_config: FidlConfig = unpersist(&config_bytes[fidl_start..]).expect("must be able to parse bytes as config FIDL");
242+
/// Parse `Self` from `vmo`.
243+
pub fn from_vmo(vmo: &zx::Vmo) -> Result<Self, Error> {
244+
let config_size = vmo.get_content_size().map_err(Error::GettingContentSize)?;
245+
let config_bytes =
246+
vmo.read_to_vec(0, config_size).map_err(Error::ReadingConfigBytes)?;
247+
Self::from_bytes(&config_bytes)
248+
}
237249

238-
Self {
250+
/// Parse `Self` from `bytes`.
251+
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
252+
let (checksum_len_bytes, bytes) =
253+
bytes.split_at_checked(2).ok_or(Error::TooFewBytes)?;
254+
let checksum_len_bytes: [u8; 2] = checksum_len_bytes
255+
.try_into()
256+
.expect("previous call guaranteed 2 element slice");
257+
let checksum_length = u16::from_le_bytes(checksum_len_bytes) as usize;
258+
let (observed_checksum, bytes) =
259+
bytes.split_at_checked(checksum_length).ok_or(Error::TooFewBytes)?;
260+
if observed_checksum != EXPECTED_CHECKSUM {
261+
return Err(Error::ChecksumMismatch {
262+
observed_checksum: observed_checksum.to_vec(),
263+
});
264+
}
265+
let fidl_config: FidlConfig = unpersist(bytes).map_err(Error::Unpersist)?;
266+
Ok(Self {
239267
snake_case_string: fidl_config.snake_case_string,
240268
lower_camel_case_string: fidl_config.lower_camel_case_string,
241269
upper_camel_case_string: fidl_config.upper_camel_case_string,
242270
const_case: fidl_config.const_case,
243271
string_that_has02_digits: fidl_config.string_that_has02_digits,
244-
mixed_lower_camel_snake_case_string: fidl_config.mixed_lower_camel_snake_case_string,
245-
mixed_upper_camel_snake_case_string: fidl_config.mixed_upper_camel_snake_case_string,
272+
mixed_lower_camel_snake_case_string: fidl_config
273+
.mixed_lower_camel_snake_case_string,
274+
mixed_upper_camel_snake_case_string: fidl_config
275+
.mixed_upper_camel_snake_case_string,
246276
multiple__underscores: fidl_config.multiple__underscores,
247277
unsafe_: fidl_config.unsafe_,
248278
server_mode_: fidl_config.server_mode_
249-
}
279+
})
250280
}
251-
pub fn record_inspect(&self, inspector_node : & Node) {
281+
pub fn record_inspect(&self, inspector_node: &Node) {
252282
inspector_node.record_bool("snake_case_string", self.snake_case_string);
253-
inspector_node.record_bool("lowerCamelCaseString", self.lower_camel_case_string);
254-
inspector_node.record_bool("UpperCamelCaseString", self.upper_camel_case_string);
283+
inspector_node
284+
.record_bool("lowerCamelCaseString", self.lower_camel_case_string);
285+
inspector_node
286+
.record_bool("UpperCamelCaseString", self.upper_camel_case_string);
255287
inspector_node.record_bool("CONST_CASE", self.const_case);
256-
inspector_node.record_bool("stringThatHas02Digits", self.string_that_has02_digits);
257-
inspector_node.record_bool("mixedLowerCamel_snakeCaseString", self.mixed_lower_camel_snake_case_string);
258-
inspector_node.record_bool("MixedUpperCamel_SnakeCaseString", self.mixed_upper_camel_snake_case_string);
288+
inspector_node
289+
.record_bool("stringThatHas02Digits", self.string_that_has02_digits);
290+
inspector_node.record_bool(
291+
"mixedLowerCamel_snakeCaseString",
292+
self.mixed_lower_camel_snake_case_string
293+
);
294+
inspector_node.record_bool(
295+
"MixedUpperCamel_SnakeCaseString",
296+
self.mixed_upper_camel_snake_case_string
297+
);
259298
inspector_node.record_bool("multiple__underscores", self.multiple__underscores);
260299
inspector_node.record_bool("unsafe", self.unsafe_);
261300
inspector_node.record_bool("ServerMode", self.server_mode_);
262301
}
263302
}
264-
}.to_string();
303+
304+
#[derive(Debug)]
305+
pub enum Error {
306+
/// Failed to read the content size of the VMO.
307+
GettingContentSize(zx::Status),
308+
/// Failed to read the content of the VMO.
309+
ReadingConfigBytes(zx::Status),
310+
/// The VMO was too small for this config library.
311+
TooFewBytes,
312+
/// The VMO's config ABI checksum did not match this library's.
313+
ChecksumMismatch { observed_checksum: Vec<u8> },
314+
/// Failed to parse the non-checksum bytes of the VMO as this library's FIDL type.
315+
Unpersist(fidl::Error),
316+
}
317+
318+
impl std::fmt::Display for Error {
319+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320+
match self {
321+
Self::GettingContentSize(status) => {
322+
write!(f, "Failed to get content size: {status}")
323+
}
324+
Self::ReadingConfigBytes(status) => {
325+
write!(f, "Failed to read VMO content: {status}")
326+
}
327+
Self::TooFewBytes => {
328+
write!(f, "VMO content is not large enough for this config library.")
329+
}
330+
Self::ChecksumMismatch { observed_checksum } => {
331+
write!(
332+
f,
333+
"ABI checksum mismatch, expected {:?}, got {:?}",
334+
EXPECTED_CHECKSUM, observed_checksum,
335+
)
336+
}
337+
Self::Unpersist(fidl_error) => {
338+
write!(f, "Failed to parse contents of config VMO: {fidl_error}")
339+
}
340+
}
341+
}
342+
}
343+
344+
impl std::error::Error for Error {
345+
#[allow(unused_parens, reason = "rustfmt errors without parens here")]
346+
fn source(&self) -> Option<(&'_ (dyn std::error::Error + 'static))> {
347+
match self {
348+
Self::GettingContentSize(ref status)
349+
| Self::ReadingConfigBytes(ref status) => Some(status),
350+
Self::TooFewBytes => None,
351+
Self::ChecksumMismatch { .. } => None,
352+
Self::Unpersist(ref fidl_error) => Some(fidl_error),
353+
}
354+
}
355+
fn description(&self) -> &str {
356+
match self {
357+
Self::GettingContentSize(_) => "getting content size",
358+
Self::ReadingConfigBytes(_) => "reading VMO contents",
359+
Self::TooFewBytes => "VMO contents too small",
360+
Self::ChecksumMismatch { .. } => "ABI checksum mismatch",
361+
Self::Unpersist(_) => "FIDL parsing error",
362+
}
363+
}
364+
}
365+
}
366+
.to_string();
265367

266368
assert_eq!(actual_rust_src, expected_rust_src);
267369
}

0 commit comments

Comments
 (0)