/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use anyhow::{bail, ensure, Context, Result}; use clap::ValueEnum; use protobuf::Message; use std::io::Read; use std::path::PathBuf; use crate::codegen_cpp::generate_cpp_code; use crate::codegen_java::generate_java_code; use crate::codegen_rust::generate_rust_code; use crate::protos::{ ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint, }; pub struct Input { pub source: String, pub reader: Box, } impl Input { fn try_parse_flags(&mut self) -> Result { let mut buffer = Vec::new(); self.reader .read_to_end(&mut buffer) .with_context(|| format!("failed to read {}", self.source))?; crate::protos::parsed_flags::try_from_binary_proto(&buffer) .with_context(|| self.error_context()) } fn error_context(&self) -> String { format!("failed to parse {}", self.source) } } pub struct OutputFile { pub path: PathBuf, // relative to some root directory only main knows about pub contents: Vec, } pub const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED; pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE; pub fn parse_flags( package: &str, container: Option<&str>, declarations: Vec, values: Vec, default_permission: ProtoFlagPermission, ) -> Result> { let mut parsed_flags = ProtoParsedFlags::new(); for mut input in declarations { let mut contents = String::new(); input .reader .read_to_string(&mut contents) .with_context(|| format!("failed to read {}", input.source))?; let flag_declarations = crate::protos::flag_declarations::try_from_text_proto(&contents) .with_context(|| input.error_context())?; ensure!( package == flag_declarations.package(), "failed to parse {}: expected package {}, got {}", input.source, package, flag_declarations.package() ); if let Some(c) = container { ensure!( c == flag_declarations.container(), "failed to parse {}: expected container {}, got {}", input.source, c, flag_declarations.container() ); } for mut flag_declaration in flag_declarations.flag.into_iter() { crate::protos::flag_declaration::verify_fields(&flag_declaration) .with_context(|| input.error_context())?; // create ParsedFlag using FlagDeclaration and default values let mut parsed_flag = ProtoParsedFlag::new(); if let Some(c) = container { parsed_flag.set_container(c.to_string()); } parsed_flag.set_package(package.to_string()); parsed_flag.set_name(flag_declaration.take_name()); parsed_flag.set_namespace(flag_declaration.take_namespace()); parsed_flag.set_description(flag_declaration.take_description()); parsed_flag.bug.append(&mut flag_declaration.bug); parsed_flag.set_state(DEFAULT_FLAG_STATE); let flag_permission = if flag_declaration.is_fixed_read_only() { ProtoFlagPermission::READ_ONLY } else { default_permission }; parsed_flag.set_permission(flag_permission); parsed_flag.set_is_fixed_read_only(flag_declaration.is_fixed_read_only()); parsed_flag.set_is_exported(flag_declaration.is_exported()); let mut tracepoint = ProtoTracepoint::new(); tracepoint.set_source(input.source.clone()); tracepoint.set_state(DEFAULT_FLAG_STATE); tracepoint.set_permission(flag_permission); parsed_flag.trace.push(tracepoint); // verify ParsedFlag looks reasonable crate::protos::parsed_flag::verify_fields(&parsed_flag)?; // verify ParsedFlag can be added ensure!( parsed_flags.parsed_flag.iter().all(|other| other.name() != parsed_flag.name()), "failed to declare flag {} from {}: flag already declared", parsed_flag.name(), input.source ); // add ParsedFlag to ParsedFlags parsed_flags.parsed_flag.push(parsed_flag); } } for mut input in values { let mut contents = String::new(); input .reader .read_to_string(&mut contents) .with_context(|| format!("failed to read {}", input.source))?; let flag_values = crate::protos::flag_values::try_from_text_proto(&contents) .with_context(|| input.error_context())?; for flag_value in flag_values.flag_value.into_iter() { crate::protos::flag_value::verify_fields(&flag_value) .with_context(|| input.error_context())?; let Some(parsed_flag) = parsed_flags .parsed_flag .iter_mut() .find(|pf| pf.package() == flag_value.package() && pf.name() == flag_value.name()) else { // (silently) skip unknown flags continue; }; ensure!( !parsed_flag.is_fixed_read_only() || flag_value.permission() == ProtoFlagPermission::READ_ONLY, "failed to set permission of flag {}, since this flag is fixed read only flag", flag_value.name() ); parsed_flag.set_state(flag_value.state()); parsed_flag.set_permission(flag_value.permission()); let mut tracepoint = ProtoTracepoint::new(); tracepoint.set_source(input.source.clone()); tracepoint.set_state(flag_value.state()); tracepoint.set_permission(flag_value.permission()); parsed_flag.trace.push(tracepoint); } } // Create a sorted parsed_flags crate::protos::parsed_flags::sort_parsed_flags(&mut parsed_flags); crate::protos::parsed_flags::verify_fields(&parsed_flags)?; let mut output = Vec::new(); parsed_flags.write_to_vec(&mut output)?; Ok(output) } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum CodegenMode { Production, Test, Exported, } pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result> { let parsed_flags = input.try_parse_flags()?; let Some(package) = find_unique_package(&parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode) } pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result> { let parsed_flags = input.try_parse_flags()?; let Some(package) = find_unique_package(&parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; generate_cpp_code(package, parsed_flags.parsed_flag.iter(), codegen_mode) } pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result { let parsed_flags = input.try_parse_flags()?; let Some(package) = find_unique_package(&parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; generate_rust_code(package, parsed_flags.parsed_flag.iter(), codegen_mode) } pub fn create_device_config_defaults(mut input: Input) -> Result> { let parsed_flags = input.try_parse_flags()?; let mut output = Vec::new(); for parsed_flag in parsed_flags .parsed_flag .into_iter() .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE) { let line = format!( "{}:{}.{}={}\n", parsed_flag.namespace(), parsed_flag.package(), parsed_flag.name(), match parsed_flag.state() { ProtoFlagState::ENABLED => "enabled", ProtoFlagState::DISABLED => "disabled", } ); output.extend_from_slice(line.as_bytes()); } Ok(output) } pub fn create_device_config_sysprops(mut input: Input) -> Result> { let parsed_flags = input.try_parse_flags()?; let mut output = Vec::new(); for parsed_flag in parsed_flags .parsed_flag .into_iter() .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE) { let line = format!( "persist.device_config.{}.{}={}\n", parsed_flag.package(), parsed_flag.name(), match parsed_flag.state() { ProtoFlagState::ENABLED => "true", ProtoFlagState::DISABLED => "false", } ); output.extend_from_slice(line.as_bytes()); } Ok(output) } #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum DumpFormat { Text, Verbose, Protobuf, Textproto, } pub fn dump_parsed_flags(mut input: Vec, format: DumpFormat, dedup: bool) -> Result> { let individually_parsed_flags: Result> = input.iter_mut().map(|i| i.try_parse_flags()).collect(); let parsed_flags: ProtoParsedFlags = crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?; let mut output = Vec::new(); match format { DumpFormat::Text => { for parsed_flag in parsed_flags.parsed_flag.into_iter() { let line = format!( "{}.{} [{}]: {:?} + {:?}\n", parsed_flag.package(), parsed_flag.name(), parsed_flag.container(), parsed_flag.permission(), parsed_flag.state() ); output.extend_from_slice(line.as_bytes()); } } DumpFormat::Verbose => { for parsed_flag in parsed_flags.parsed_flag.into_iter() { let sources: Vec<_> = parsed_flag.trace.iter().map(|tracepoint| tracepoint.source()).collect(); let line = format!( "{}.{} [{}]: {:?} + {:?} ({})\n", parsed_flag.package(), parsed_flag.name(), parsed_flag.container(), parsed_flag.permission(), parsed_flag.state(), sources.join(", ") ); output.extend_from_slice(line.as_bytes()); } } DumpFormat::Protobuf => { parsed_flags.write_to_vec(&mut output)?; } DumpFormat::Textproto => { let s = protobuf::text_format::print_to_string_pretty(&parsed_flags); output.extend_from_slice(s.as_bytes()); } } Ok(output) } fn find_unique_package(parsed_flags: &ProtoParsedFlags) -> Option<&str> { let Some(package) = parsed_flags.parsed_flag.first().map(|pf| pf.package()) else { return None; }; if parsed_flags.parsed_flag.iter().any(|pf| pf.package() != package) { return None; } Some(package) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_flags() { let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags crate::protos::parsed_flags::verify_fields(&parsed_flags).unwrap(); let enabled_ro = parsed_flags.parsed_flag.iter().find(|pf| pf.name() == "enabled_ro").unwrap(); assert!(crate::protos::parsed_flag::verify_fields(enabled_ro).is_ok()); assert_eq!("com.android.aconfig.test", enabled_ro.package()); assert_eq!("enabled_ro", enabled_ro.name()); assert_eq!("This flag is ENABLED + READ_ONLY", enabled_ro.description()); assert_eq!(ProtoFlagState::ENABLED, enabled_ro.state()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.permission()); assert_eq!(3, enabled_ro.trace.len()); assert!(!enabled_ro.is_fixed_read_only()); assert_eq!("tests/test.aconfig", enabled_ro.trace[0].source()); assert_eq!(ProtoFlagState::DISABLED, enabled_ro.trace[0].state()); assert_eq!(ProtoFlagPermission::READ_WRITE, enabled_ro.trace[0].permission()); assert_eq!("tests/first.values", enabled_ro.trace[1].source()); assert_eq!(ProtoFlagState::DISABLED, enabled_ro.trace[1].state()); assert_eq!(ProtoFlagPermission::READ_WRITE, enabled_ro.trace[1].permission()); assert_eq!("tests/second.values", enabled_ro.trace[2].source()); assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission()); assert_eq!(7, parsed_flags.parsed_flag.len()); for pf in parsed_flags.parsed_flag.iter() { if pf.name() == "enabled_fixed_ro" { continue; } let first = pf.trace.first().unwrap(); assert_eq!(DEFAULT_FLAG_STATE, first.state()); assert_eq!(DEFAULT_FLAG_PERMISSION, first.permission()); let last = pf.trace.last().unwrap(); assert_eq!(pf.state(), last.state()); assert_eq!(pf.permission(), last.permission()); } let enabled_fixed_ro = parsed_flags.parsed_flag.iter().find(|pf| pf.name() == "enabled_fixed_ro").unwrap(); assert!(enabled_fixed_ro.is_fixed_read_only()); assert_eq!(ProtoFlagState::ENABLED, enabled_fixed_ro.state()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_fixed_ro.permission()); assert_eq!(2, enabled_fixed_ro.trace.len()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_fixed_ro.trace[0].permission()); assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_fixed_ro.trace[1].permission()); } #[test] fn test_parse_flags_setting_default() { let first_flag = r#" package: "com.first" flag { name: "first" namespace: "first_ns" description: "This is the description of the first flag." bug: "123" } "#; let declaration = vec![Input { source: "momery".to_string(), reader: Box::new(first_flag.as_bytes()) }]; let value: Vec = vec![]; let flags_bytes = crate::commands::parse_flags( "com.first", None, declaration, value, ProtoFlagPermission::READ_ONLY, ) .unwrap(); let parsed_flags = crate::protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state()); assert_eq!(ProtoFlagPermission::READ_ONLY, parsed_flag.permission()); } #[test] fn test_parse_flags_package_mismatch_between_declaration_and_command_line() { let first_flag = r#" package: "com.declaration.package" container: "first.container" flag { name: "first" namespace: "first_ns" description: "This is the description of the first flag." bug: "123" } "#; let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }]; let value: Vec = vec![]; let error = crate::commands::parse_flags( "com.argument.package", Some("first.container"), declaration, value, ProtoFlagPermission::READ_WRITE, ) .unwrap_err(); assert_eq!( format!("{:?}", error), "failed to parse memory: expected package com.argument.package, got com.declaration.package" ); } #[test] fn test_parse_flags_container_mismatch_between_declaration_and_command_line() { let first_flag = r#" package: "com.first" container: "declaration.container" flag { name: "first" namespace: "first_ns" description: "This is the description of the first flag." bug: "123" } "#; let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }]; let value: Vec = vec![]; let error = crate::commands::parse_flags( "com.first", Some("argument.container"), declaration, value, ProtoFlagPermission::READ_WRITE, ) .unwrap_err(); assert_eq!( format!("{:?}", error), "failed to parse memory: expected container argument.container, got declaration.container" ); } #[test] fn test_parse_flags_override_fixed_read_only() { let first_flag = r#" package: "com.first" container: "com.first.container" flag { name: "first" namespace: "first_ns" description: "This is the description of the first flag." bug: "123" is_fixed_read_only: true } "#; let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }]; let first_flag_value = r#" flag_value { package: "com.first" name: "first" state: DISABLED permission: READ_WRITE } "#; let value = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag_value.as_bytes()), }]; let error = crate::commands::parse_flags( "com.first", Some("com.first.container"), declaration, value, ProtoFlagPermission::READ_WRITE, ) .unwrap_err(); assert_eq!( format!("{:?}", error), "failed to set permission of flag first, since this flag is fixed read only flag" ); } #[test] fn test_create_device_config_defaults() { let input = parse_test_flags_as_input(); let bytes = create_device_config_defaults(input).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\naconfig_test:com.android.aconfig.test.disabled_rw_exported=disabled\nother_namespace:com.android.aconfig.test.disabled_rw_in_other_namespace=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text); } #[test] fn test_create_device_config_sysprops() { let input = parse_test_flags_as_input(); let bytes = create_device_config_sysprops(input).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.disabled_rw_exported=false\npersist.device_config.com.android.aconfig.test.disabled_rw_in_other_namespace=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text); } #[test] fn test_dump_text_format() { let input = parse_test_flags_as_input(); let bytes = dump_parsed_flags(vec![input], DumpFormat::Text, false).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); assert!( text.contains("com.android.aconfig.test.disabled_ro [system]: READ_ONLY + DISABLED") ); } #[test] fn test_dump_protobuf_format() { let expected = protobuf::text_format::parse_from_str::( crate::test::TEST_FLAGS_TEXTPROTO, ) .unwrap() .write_to_bytes() .unwrap(); let input = parse_test_flags_as_input(); let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf, false).unwrap(); assert_eq!(expected, actual); } #[test] fn test_dump_textproto_format() { let input = parse_test_flags_as_input(); let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto, false).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim()); } #[test] fn test_dump_textproto_format_dedup() { let input = parse_test_flags_as_input(); let input2 = parse_test_flags_as_input(); let bytes = dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, true).unwrap(); let text = std::str::from_utf8(&bytes).unwrap(); assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim()); } fn parse_test_flags_as_input() -> Input { let parsed_flags = crate::test::parse_test_flags(); let binary_proto = parsed_flags.write_to_bytes().unwrap(); let cursor = std::io::Cursor::new(binary_proto); let reader = Box::new(cursor); Input { source: "test.data".to_string(), reader } } }