1use fidl_fuchsia_diagnostics_common::{Selector, StringSelector, TreeNames};
6use selectors::FastError;
7use serde::{Deserialize, Deserializer, de};
8use std::fmt;
9use std::marker::PhantomData;
10use std::sync::LazyLock;
11use thiserror::Error;
12
13pub fn greater_than_zero<'de, D>(deserializer: D) -> Result<i64, D::Error>
14where
15 D: Deserializer<'de>,
16{
17 let value = i64::deserialize(deserializer)?;
18 if value <= 0 {
19 return Err(de::Error::custom(format!("i64 out of range: {value}")));
20 }
21 Ok(value)
22}
23
24pub fn one_or_many_selectors<'de, D>(deserializer: D) -> Result<Vec<Selector>, D::Error>
25where
26 D: Deserializer<'de>,
27{
28 deserializer.deserialize_any(OneOrMany(PhantomData::<Selector>))
29}
30
31pub fn one_or_many_strings<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
32where
33 D: Deserializer<'de>,
34{
35 deserializer.deserialize_any(OneOrMany(PhantomData::<String>))
36}
37
38pub(crate) struct OneOrMany<T>(pub PhantomData<T>);
39
40trait ParseString {
41 fn parse_string(value: &str) -> Result<Self, Error>
42 where
43 Self: Sized;
44}
45
46impl ParseString for String {
47 fn parse_string(value: &str) -> Result<Self, Error> {
48 Ok(value.into())
49 }
50}
51
52impl ParseString for Selector {
53 fn parse_string(value: &str) -> Result<Self, Error> {
54 parse_selector(value)
55 }
56}
57
58impl<'de, T> de::Visitor<'de> for OneOrMany<T>
59where
60 T: ParseString,
61{
62 type Value = Vec<T>;
63
64 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str("either a single string or an array of strings")
66 }
67
68 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
69 where
70 E: de::Error,
71 {
72 let result = T::parse_string(value).map_err(E::custom)?;
73 Ok(vec![result])
74 }
75
76 fn visit_seq<S>(self, mut visitor: S) -> Result<Self::Value, S::Error>
77 where
78 S: de::SeqAccess<'de>,
79 {
80 let mut out = vec![];
81 while let Some(s) = visitor.next_element::<String>()? {
82 use serde::de::Error;
83 let selector = T::parse_string(&s).map_err(S::Error::custom)?;
84 out.push(selector);
85 }
86 if out.is_empty() {
87 Err(de::Error::invalid_length(0, &"expected at least one string"))
88 } else {
89 out.shrink_to_fit();
90 Ok(out)
91 }
92 }
93}
94
95pub fn parse_selector(selector_str: &str) -> Result<Selector, Error> {
96 let selector = selectors::parse_selector::<FastError>(selector_str)?;
97 verify_wildcard_restrictions(&selector, selector_str)?;
98 Ok(selector)
99}
100
101struct WildcardRestriction {
102 segments: Vec<StringSelector>,
103 must_have_tree_name: bool,
104}
105
106static WILDCARD_RESTRICTIONS: LazyLock<Vec<WildcardRestriction>> = LazyLock::new(|| {
107 vec![
108 WildcardRestriction {
109 segments: vec![
110 StringSelector::ExactMatch("core".into()),
111 StringSelector::ExactMatch("bluetooth-core".into()),
112 StringSelector::StringPattern("bt-host-collection:bt-host_*".into()),
113 ],
114 must_have_tree_name: false,
115 },
116 WildcardRestriction {
117 segments: vec![
118 StringSelector::ExactMatch("bootstrap".into()),
119 StringSelector::StringPattern("*-drivers:*".into()),
120 ],
121 must_have_tree_name: true,
122 },
123 WildcardRestriction {
124 segments: vec![
125 StringSelector::ExactMatch("bootstrap".into()),
126 StringSelector::ExactMatch("fshost".into()),
127 StringSelector::ExactMatch("fvm2".into()),
128 StringSelector::StringPattern("blobfs-collection*".into()),
129 ],
130 must_have_tree_name: false,
131 },
132 WildcardRestriction {
133 segments: vec![
134 StringSelector::ExactMatch("bootstrap".into()),
135 StringSelector::ExactMatch("fshost".into()),
136 StringSelector::ExactMatch("fvm2".into()),
137 StringSelector::StringPattern("fs-collection*".into()),
138 ],
139 must_have_tree_name: false,
140 },
141 ]
142});
143
144fn verify_wildcard_restrictions(selector: &Selector, raw_selector: &str) -> Result<(), Error> {
146 let actual_segments =
149 selector.component_selector.as_ref().unwrap().moniker_segments.as_ref().unwrap();
150 if !actual_segments.iter().any(|segment| matches!(segment, StringSelector::StringPattern(_))) {
151 return Ok(());
152 }
153 for restriction in &*WILDCARD_RESTRICTIONS {
154 if restriction.segments.len() != actual_segments.len() {
155 continue;
156 }
157 if restriction
158 .segments
159 .iter()
160 .zip(actual_segments.iter())
161 .any(|(expected_segment, actual_segment)| expected_segment != actual_segment)
162 {
163 continue;
164 }
165 if restriction.must_have_tree_name {
166 let Some(TreeNames::Some(_)) = selector.tree_names else {
167 return Err(Error::InvalidWildcardedSelector(raw_selector.to_string()));
168 };
169 }
170 return Ok(());
171 }
172 Err(Error::InvalidWildcardedSelector(raw_selector.to_string()))
173}
174
175#[derive(Debug, Error)]
176pub enum Error {
177 #[error("wildcarded component not allowlisted: '{0}'")]
178 InvalidWildcardedSelector(String),
179
180 #[error(transparent)]
181 ParseSelector(#[from] selectors::Error),
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use assert_matches::assert_matches;
188
189 #[derive(Debug, Deserialize, PartialEq)]
190 struct Test(#[serde(deserialize_with = "super::one_or_many_selectors")] Vec<Selector>);
191
192 #[derive(Debug, Deserialize, Eq, PartialEq)]
193 struct TestInt(#[serde(deserialize_with = "super::greater_than_zero")] i64);
194
195 #[fuchsia::test]
196 fn parse_valid_single_selector() {
197 let json = "\"core/bar:root/twig:leaf\"";
198 let data: Test = serde_json5::from_str(json).expect("deserialize");
199 assert_eq!(
200 data,
201 Test(vec![selectors::parse_selector::<FastError>("core/bar:root/twig:leaf").unwrap()])
202 );
203 }
204
205 #[fuchsia::test]
206 fn parse_valid_multiple_selectors() {
207 let json = "[ \"core/foo:some/branch:leaf\", \"core/bar:root/twig:leaf\"]";
208 let data: Test = serde_json5::from_str(json).expect("deserialize");
209 assert_eq!(
210 data,
211 Test(vec![
212 selectors::parse_selector::<FastError>("core/foo:some/branch:leaf").unwrap(),
213 selectors::parse_selector::<FastError>("core/bar:root/twig:leaf").unwrap()
214 ])
215 );
216 }
217
218 #[fuchsia::test]
219 fn refuse_invalid_selectors() {
220 let not_string = "42";
221 let bad_list = "[ 42, \"core/bar:not:a:selector:root/twig:leaf\"]";
222 serde_json5::from_str::<Test>(not_string).expect_err("should fail");
223 serde_json5::from_str::<Test>(bad_list).expect_err("should fail");
224 }
225
226 #[fuchsia::test]
227 fn test_greater_than_zero() {
228 let data: TestInt = serde_json5::from_str("1").unwrap();
229 assert_eq!(data, TestInt(1));
230 serde_json5::from_str::<Test>("0").expect_err("0 is not greater than 0");
231 serde_json5::from_str::<Test>("-1").expect_err("-1 is not greater than 0");
232 }
233
234 #[fuchsia::test]
235 fn wild_card_selectors() {
236 let good_selector = r#"["bootstrap/*-drivers\\:*:[name=fvm]root:field"]"#;
237 assert_matches!(serde_json5::from_str::<Test>(good_selector), Ok(_));
238
239 let good_selector = r#"["core/bluetooth-core/bt-host-collection\\:bt-host_*:root:field"]"#;
240 assert_matches!(serde_json5::from_str::<Test>(good_selector), Ok(_));
241
242 let bad_selector = r#"["not_bootstrap/*-drivers\\:*:[name=fvm]root:field"]"#;
243 assert_matches!(serde_json5::from_str::<Test>(bad_selector), Err(_));
244
245 let not_exact_collection_match = r#"["bootstrap/*-drivers*:[name=fvm]root:field"]"#;
246 assert_matches!(serde_json5::from_str::<Test>(not_exact_collection_match), Err(_));
247
248 let missing_filter = r#"["not_bootstrap/*-drivers\\:*:root:field"]"#;
249 assert_matches!(serde_json5::from_str::<Test>(missing_filter), Err(_));
250 }
251}