1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use aconfig_protos::{
18     ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint,
19 };
20 use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
21 use anyhow::{anyhow, bail, Context, Result};
22 use protobuf::Message;
23 
24 #[derive(Clone, Debug, PartialEq, Eq)]
25 pub enum DumpFormat {
26     Protobuf,
27     Textproto,
28     Custom(String),
29 }
30 
31 impl TryFrom<&str> for DumpFormat {
32     type Error = anyhow::Error;
33 
try_from(value: &str) -> std::result::Result<Self, Self::Error>34     fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
35         match value {
36             // protobuf formats
37             "protobuf" => Ok(Self::Protobuf),
38             "textproto" => Ok(Self::Textproto),
39             // custom format
40             _ => Ok(Self::Custom(value.to_owned())),
41         }
42     }
43 }
44 
dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>> where I: Iterator<Item = ProtoParsedFlag>,45 pub fn dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>>
46 where
47     I: Iterator<Item = ProtoParsedFlag>,
48 {
49     let mut output = Vec::new();
50     match format {
51         DumpFormat::Protobuf => {
52             let parsed_flags =
53                 ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
54             parsed_flags.write_to_vec(&mut output)?;
55         }
56         DumpFormat::Textproto => {
57             let parsed_flags =
58                 ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
59             let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
60             output.extend_from_slice(s.as_bytes());
61         }
62         DumpFormat::Custom(format) => {
63             for flag in parsed_flags_iter {
64                 dump_custom_format(&flag, &format, &mut output);
65             }
66         }
67     }
68     Ok(output)
69 }
70 
dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>)71 fn dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>) {
72     fn format_trace(trace: &[ProtoTracepoint]) -> String {
73         trace
74             .iter()
75             .map(|tracepoint| {
76                 format!(
77                     "{}: {:?} + {:?}",
78                     tracepoint.source(),
79                     tracepoint.permission(),
80                     tracepoint.state()
81                 )
82             })
83             .collect::<Vec<_>>()
84             .join(", ")
85     }
86 
87     fn format_trace_paths(trace: &[ProtoTracepoint]) -> String {
88         trace.iter().map(|tracepoint| tracepoint.source()).collect::<Vec<_>>().join(", ")
89     }
90 
91     fn format_metadata(metadata: &ProtoFlagMetadata) -> String {
92         format!("{:?}", metadata.purpose())
93     }
94 
95     let mut str = format
96         // ProtoParsedFlag fields
97         .replace("{package}", flag.package())
98         .replace("{name}", flag.name())
99         .replace("{namespace}", flag.namespace())
100         .replace("{description}", flag.description())
101         .replace("{bug}", &flag.bug.join(", "))
102         .replace("{state}", &format!("{:?}", flag.state()))
103         .replace("{state:bool}", &format!("{}", flag.state() == ProtoFlagState::ENABLED))
104         .replace("{permission}", &format!("{:?}", flag.permission()))
105         .replace("{trace}", &format_trace(&flag.trace))
106         .replace("{trace:paths}", &format_trace_paths(&flag.trace))
107         .replace("{is_fixed_read_only}", &format!("{}", flag.is_fixed_read_only()))
108         .replace("{is_exported}", &format!("{}", flag.is_exported()))
109         .replace("{container}", flag.container())
110         .replace("{metadata}", &format_metadata(&flag.metadata))
111         // ParsedFlagExt functions
112         .replace("{fully_qualified_name}", &flag.fully_qualified_name());
113     str.push('\n');
114     output.extend_from_slice(str.as_bytes());
115 }
116 
117 pub type DumpPredicate = dyn Fn(&ProtoParsedFlag) -> bool;
118 
create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>>119 pub fn create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>> {
120     let predicates = filter
121         .split('+')
122         .map(|sub_filter| create_filter_predicate_single(sub_filter))
123         .collect::<Result<Vec<_>>>()?;
124     Ok(Box::new(move |flag| predicates.iter().all(|p| p(flag))))
125 }
126 
create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>>127 fn create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>> {
128     fn enum_from_str<T>(expected: &[T], s: &str) -> Result<T>
129     where
130         T: std::fmt::Debug + Copy,
131     {
132         for candidate in expected.iter() {
133             if s == format!("{:?}", candidate) {
134                 return Ok(*candidate);
135             }
136         }
137         let expected =
138             expected.iter().map(|state| format!("{:?}", state)).collect::<Vec<_>>().join(", ");
139         bail!("\"{s}\": not a valid flag state, expected one of {expected}");
140     }
141 
142     let error_msg = format!("\"{filter}\": filter syntax error");
143     let (what, arg) = filter.split_once(':').ok_or_else(|| anyhow!(error_msg.clone()))?;
144     match what {
145         "package" => {
146             let expected = arg.to_owned();
147             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.package() == expected))
148         }
149         "name" => {
150             let expected = arg.to_owned();
151             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.name() == expected))
152         }
153         "namespace" => {
154             let expected = arg.to_owned();
155             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.namespace() == expected))
156         }
157         // description: not supported yet
158         "bug" => {
159             let expected = arg.to_owned();
160             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.bug.join(", ") == expected))
161         }
162         "state" => {
163             let expected = enum_from_str(&[ProtoFlagState::ENABLED, ProtoFlagState::DISABLED], arg)
164                 .context(error_msg)?;
165             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.state() == expected))
166         }
167         "permission" => {
168             let expected = enum_from_str(
169                 &[ProtoFlagPermission::READ_ONLY, ProtoFlagPermission::READ_WRITE],
170                 arg,
171             )
172             .context(error_msg)?;
173             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.permission() == expected))
174         }
175         // trace: not supported yet
176         "is_fixed_read_only" => {
177             let expected: bool = arg.parse().context(error_msg)?;
178             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_fixed_read_only() == expected))
179         }
180         "is_exported" => {
181             let expected: bool = arg.parse().context(error_msg)?;
182             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_exported() == expected))
183         }
184         "container" => {
185             let expected = arg.to_owned();
186             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
187         }
188         // metadata: not supported yet
189         "fully_qualified_name" => {
190             let expected = arg.to_owned();
191             Ok(Box::new(move |flag: &ProtoParsedFlag| flag.fully_qualified_name() == expected))
192         }
193         _ => Err(anyhow!(error_msg)),
194     }
195 }
196 
197 #[cfg(test)]
198 mod tests {
199     use super::*;
200     use crate::test::parse_test_flags;
201     use aconfig_protos::ProtoParsedFlags;
202     use protobuf::Message;
203 
parse_enabled_ro_flag() -> ProtoParsedFlag204     fn parse_enabled_ro_flag() -> ProtoParsedFlag {
205         parse_test_flags().parsed_flag.into_iter().find(|pf| pf.name() == "enabled_ro").unwrap()
206     }
207 
208     #[test]
test_dumpformat_from_str()209     fn test_dumpformat_from_str() {
210         // supported format types
211         assert_eq!(DumpFormat::try_from("protobuf").unwrap(), DumpFormat::Protobuf);
212         assert_eq!(DumpFormat::try_from("textproto").unwrap(), DumpFormat::Textproto);
213         assert_eq!(
214             DumpFormat::try_from("foobar").unwrap(),
215             DumpFormat::Custom("foobar".to_owned())
216         );
217     }
218 
219     #[test]
test_dump_parsed_flags_protobuf_format()220     fn test_dump_parsed_flags_protobuf_format() {
221         let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
222             crate::test::TEST_FLAGS_TEXTPROTO,
223         )
224         .unwrap()
225         .write_to_bytes()
226         .unwrap();
227         let parsed_flags = parse_test_flags();
228         let actual =
229             dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Protobuf).unwrap();
230         assert_eq!(expected, actual);
231     }
232 
233     #[test]
test_dump_parsed_flags_textproto_format()234     fn test_dump_parsed_flags_textproto_format() {
235         let parsed_flags = parse_test_flags();
236         let bytes =
237             dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Textproto).unwrap();
238         let text = std::str::from_utf8(&bytes).unwrap();
239         assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
240     }
241 
242     #[test]
test_dump_parsed_flags_custom_format()243     fn test_dump_parsed_flags_custom_format() {
244         macro_rules! assert_dump_parsed_flags_custom_format_contains {
245             ($format:expr, $expected:expr) => {
246                 let parsed_flags = parse_test_flags();
247                 let bytes = dump_parsed_flags(
248                     parsed_flags.parsed_flag.into_iter(),
249                     $format.try_into().unwrap(),
250                 )
251                 .unwrap();
252                 let text = std::str::from_utf8(&bytes).unwrap();
253                 assert!(text.contains($expected));
254             };
255         }
256 
257         // custom format
258         assert_dump_parsed_flags_custom_format_contains!(
259             "{fully_qualified_name}={permission} + {state}",
260             "com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
261         );
262     }
263 
264     #[test]
test_dump_custom_format()265     fn test_dump_custom_format() {
266         macro_rules! assert_custom_format {
267             ($format:expr, $expected:expr) => {
268                 let flag = parse_enabled_ro_flag();
269                 let mut bytes = vec![];
270                 dump_custom_format(&flag, $format, &mut bytes);
271                 let text = std::str::from_utf8(&bytes).unwrap();
272                 assert_eq!(text, $expected);
273             };
274         }
275 
276         assert_custom_format!("{package}", "com.android.aconfig.test\n");
277         assert_custom_format!("{name}", "enabled_ro\n");
278         assert_custom_format!("{namespace}", "aconfig_test\n");
279         assert_custom_format!("{description}", "This flag is ENABLED + READ_ONLY\n");
280         assert_custom_format!("{bug}", "abc\n");
281         assert_custom_format!("{state}", "ENABLED\n");
282         assert_custom_format!("{state:bool}", "true\n");
283         assert_custom_format!("{permission}", "READ_ONLY\n");
284         assert_custom_format!("{trace}", "tests/test.aconfig: READ_WRITE + DISABLED, tests/first.values: READ_WRITE + DISABLED, tests/second.values: READ_ONLY + ENABLED\n");
285         assert_custom_format!(
286             "{trace:paths}",
287             "tests/test.aconfig, tests/first.values, tests/second.values\n"
288         );
289         assert_custom_format!("{is_fixed_read_only}", "false\n");
290         assert_custom_format!("{is_exported}", "false\n");
291         assert_custom_format!("{container}", "system\n");
292         assert_custom_format!("{metadata}", "PURPOSE_BUGFIX\n");
293 
294         assert_custom_format!("name={name}|state={state}", "name=enabled_ro|state=ENABLED\n");
295         assert_custom_format!("{state}{state}{state}", "ENABLEDENABLEDENABLED\n");
296     }
297 
298     #[test]
test_create_filter_predicate()299     fn test_create_filter_predicate() {
300         macro_rules! assert_create_filter_predicate {
301             ($filter:expr, $expected:expr) => {
302                 let parsed_flags = parse_test_flags();
303                 let predicate = create_filter_predicate($filter).unwrap();
304                 let mut filtered_flags: Vec<String> = parsed_flags
305                     .parsed_flag
306                     .into_iter()
307                     .filter(predicate)
308                     .map(|flag| flag.fully_qualified_name())
309                     .collect();
310                 filtered_flags.sort();
311                 assert_eq!(&filtered_flags, $expected);
312             };
313         }
314 
315         assert_create_filter_predicate!(
316             "package:com.android.aconfig.test",
317             &[
318                 "com.android.aconfig.test.disabled_ro",
319                 "com.android.aconfig.test.disabled_rw",
320                 "com.android.aconfig.test.disabled_rw_exported",
321                 "com.android.aconfig.test.disabled_rw_in_other_namespace",
322                 "com.android.aconfig.test.enabled_fixed_ro",
323                 "com.android.aconfig.test.enabled_fixed_ro_exported",
324                 "com.android.aconfig.test.enabled_ro",
325                 "com.android.aconfig.test.enabled_ro_exported",
326                 "com.android.aconfig.test.enabled_rw",
327             ]
328         );
329         assert_create_filter_predicate!(
330             "name:disabled_rw",
331             &["com.android.aconfig.test.disabled_rw"]
332         );
333         assert_create_filter_predicate!(
334             "namespace:other_namespace",
335             &["com.android.aconfig.test.disabled_rw_in_other_namespace"]
336         );
337         // description: not supported yet
338         assert_create_filter_predicate!("bug:123", &["com.android.aconfig.test.disabled_ro",]);
339         assert_create_filter_predicate!(
340             "state:ENABLED",
341             &[
342                 "com.android.aconfig.test.enabled_fixed_ro",
343                 "com.android.aconfig.test.enabled_fixed_ro_exported",
344                 "com.android.aconfig.test.enabled_ro",
345                 "com.android.aconfig.test.enabled_ro_exported",
346                 "com.android.aconfig.test.enabled_rw",
347             ]
348         );
349         assert_create_filter_predicate!(
350             "permission:READ_ONLY",
351             &[
352                 "com.android.aconfig.test.disabled_ro",
353                 "com.android.aconfig.test.enabled_fixed_ro",
354                 "com.android.aconfig.test.enabled_fixed_ro_exported",
355                 "com.android.aconfig.test.enabled_ro",
356                 "com.android.aconfig.test.enabled_ro_exported",
357             ]
358         );
359         // trace: not supported yet
360         assert_create_filter_predicate!(
361             "is_fixed_read_only:true",
362             &[
363                 "com.android.aconfig.test.enabled_fixed_ro",
364                 "com.android.aconfig.test.enabled_fixed_ro_exported",
365             ]
366         );
367         assert_create_filter_predicate!(
368             "is_exported:true",
369             &[
370                 "com.android.aconfig.test.disabled_rw_exported",
371                 "com.android.aconfig.test.enabled_fixed_ro_exported",
372                 "com.android.aconfig.test.enabled_ro_exported",
373             ]
374         );
375         assert_create_filter_predicate!(
376             "container:system",
377             &[
378                 "com.android.aconfig.test.disabled_ro",
379                 "com.android.aconfig.test.disabled_rw",
380                 "com.android.aconfig.test.disabled_rw_exported",
381                 "com.android.aconfig.test.disabled_rw_in_other_namespace",
382                 "com.android.aconfig.test.enabled_fixed_ro",
383                 "com.android.aconfig.test.enabled_fixed_ro_exported",
384                 "com.android.aconfig.test.enabled_ro",
385                 "com.android.aconfig.test.enabled_ro_exported",
386                 "com.android.aconfig.test.enabled_rw",
387             ]
388         );
389         // metadata: not supported yet
390 
391         // synthesized fields
392         assert_create_filter_predicate!(
393             "fully_qualified_name:com.android.aconfig.test.disabled_rw",
394             &["com.android.aconfig.test.disabled_rw"]
395         );
396 
397         // multiple sub filters
398         assert_create_filter_predicate!(
399             "permission:READ_ONLY+state:ENABLED",
400             &[
401                 "com.android.aconfig.test.enabled_fixed_ro",
402                 "com.android.aconfig.test.enabled_fixed_ro_exported",
403                 "com.android.aconfig.test.enabled_ro",
404                 "com.android.aconfig.test.enabled_ro_exported",
405             ]
406         );
407     }
408 }
409