Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30950789 authored by Mårten Kongstad's avatar Mårten Kongstad
Browse files

aconfig: introduce namespace, rename proto messages

Flags belong to a namespace. Update the proto files to reflect this.

Config files can only refer to a single namespace. Override files can
refer to multiple namespaces; an override directive for a flag in a
different namespace than the one represented by the cache will be
silently ignored.

Rename the proto messages to make it more clear what they are. Propagate
this change through the wrappers in aconfig.rs and the rest of the code.

Also, settle on Tracepoint instead of TracePoint.

Bug: 279485059
Test: atest aconfig.test
Change-Id: I16e69dd14687bc498b2ba89d6a35879459903801
parent a102909e
Loading
Loading
Loading
Loading
+35 −25
Original line number Diff line number Diff line
@@ -20,56 +20,66 @@ syntax = "proto2";

package android.aconfig;

// messages used in both aconfig input and output

enum flag_state {
  ENABLED = 1;
  DISABLED = 2;
}

enum permission {
enum flag_permission {
  READ_ONLY = 1;
  READ_WRITE = 2;
}

message value {
// aconfig input messages: configuration and override data

message flag_value {
  required flag_state state = 1;
  required permission permission = 2;
  required flag_permission permission = 2;
  optional uint32 since = 3;
}

message flag {
  required string id = 1;
message flag_definition {
  required string name = 1;
  required string description = 2;
  repeated value value = 3;
  repeated flag_value value = 3;
};

message android_config {
  repeated flag flag = 1;
message namespace {
  required string namespace = 1;
  repeated flag_definition flag = 2;
};

message override {
  required string id = 1;
  required flag_state state = 2;
  required permission permission = 3;
message flag_override {
  required string namespace = 1;
  required string name = 2;
  required flag_state state = 3;
  required flag_permission permission = 4;
};

message override_config {
  repeated override override = 1;
message flag_overrides {
  repeated flag_override flag_override = 1;
};

message dump {
  repeated dump_item item = 1;
}
// aconfig output messages: parsed and verified configuration and override data

message dump_trace {
message tracepoint {
  // path to config or override file releative to $TOP
  required string source = 1;
  required flag_state state = 2;
  required permission permission = 3;
  required flag_permission permission = 3;
}

message dump_item {
  required string id = 1;
  required string description = 2;
  required flag_state state = 3;
  required permission permission = 4;
  repeated dump_trace trace = 5;
message parsed_flag {
  required string namespace = 1;
  required string name = 2;
  required string description = 3;
  required flag_state state = 4;
  required flag_permission permission = 5;
  repeated tracepoint trace = 6;
}

message parsed_flags {
  repeated parsed_flag parsed_flag = 1;
}
+106 −79
Original line number Diff line number Diff line
@@ -18,10 +18,11 @@ use anyhow::{anyhow, Context, Error, Result};
use protobuf::{Enum, EnumOrUnknown};
use serde::{Deserialize, Serialize};

use crate::cache::{Cache, Item, TracePoint};
use crate::cache::{Cache, Item, Tracepoint};
use crate::protos::{
    ProtoAndroidConfig, ProtoDump, ProtoDumpItem, ProtoDumpTracePoint, ProtoFlag, ProtoFlagState,
    ProtoOverride, ProtoOverrideConfig, ProtoPermission, ProtoValue,
    ProtoFlagDefinition, ProtoFlagDefinitionValue, ProtoFlagOverride, ProtoFlagOverrides,
    ProtoFlagPermission, ProtoFlagState, ProtoNamespace, ProtoParsedFlag, ProtoParsedFlags,
    ProtoTracepoint,
};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
@@ -57,23 +58,23 @@ pub enum Permission {
    ReadWrite,
}

impl TryFrom<EnumOrUnknown<ProtoPermission>> for Permission {
impl TryFrom<EnumOrUnknown<ProtoFlagPermission>> for Permission {
    type Error = Error;

    fn try_from(proto: EnumOrUnknown<ProtoPermission>) -> Result<Self, Self::Error> {
        match ProtoPermission::from_i32(proto.value()) {
            Some(ProtoPermission::READ_ONLY) => Ok(Permission::ReadOnly),
            Some(ProtoPermission::READ_WRITE) => Ok(Permission::ReadWrite),
    fn try_from(proto: EnumOrUnknown<ProtoFlagPermission>) -> Result<Self, Self::Error> {
        match ProtoFlagPermission::from_i32(proto.value()) {
            Some(ProtoFlagPermission::READ_ONLY) => Ok(Permission::ReadOnly),
            Some(ProtoFlagPermission::READ_WRITE) => Ok(Permission::ReadWrite),
            None => Err(anyhow!("unknown permission enum value {}", proto.value())),
        }
    }
}

impl From<Permission> for ProtoPermission {
impl From<Permission> for ProtoFlagPermission {
    fn from(permission: Permission) -> Self {
        match permission {
            Permission::ReadOnly => ProtoPermission::READ_ONLY,
            Permission::ReadWrite => ProtoPermission::READ_WRITE,
            Permission::ReadOnly => ProtoFlagPermission::READ_ONLY,
            Permission::ReadWrite => ProtoFlagPermission::READ_WRITE,
        }
    }
}
@@ -96,10 +97,10 @@ impl Value {
    }
}

impl TryFrom<ProtoValue> for Value {
impl TryFrom<ProtoFlagDefinitionValue> for Value {
    type Error = Error;

    fn try_from(proto: ProtoValue) -> Result<Self, Self::Error> {
    fn try_from(proto: ProtoFlagDefinitionValue) -> Result<Self, Self::Error> {
        let Some(proto_state) = proto.state else {
            return Err(anyhow!("missing 'state' field"));
        };
@@ -114,7 +115,7 @@ impl TryFrom<ProtoValue> for Value {

#[derive(Debug, PartialEq, Eq)]
pub struct Flag {
    pub id: String,
    pub name: String,
    pub description: String,

    // ordered by Value.since; guaranteed to contain at least one item (the default value, with
@@ -125,17 +126,11 @@ pub struct Flag {
impl Flag {
    #[allow(dead_code)] // only used in unit tests
    pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
        let proto: ProtoFlag = crate::protos::try_from_text_proto(text_proto)
        let proto: ProtoFlagDefinition = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        proto.try_into()
    }

    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Flag>> {
        let proto: ProtoAndroidConfig = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        proto.flag.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
    }

    pub fn resolve(&self, build_id: u32) -> (FlagState, Permission) {
        let mut state = self.values[0].state;
        let mut permission = self.values[0].permission;
@@ -150,12 +145,12 @@ impl Flag {
    }
}

impl TryFrom<ProtoFlag> for Flag {
impl TryFrom<ProtoFlagDefinition> for Flag {
    type Error = Error;

    fn try_from(proto: ProtoFlag) -> Result<Self, Self::Error> {
        let Some(id) = proto.id else {
            return Err(anyhow!("missing 'id' field"));
    fn try_from(proto: ProtoFlagDefinition) -> Result<Self, Self::Error> {
        let Some(name) = proto.name else {
            return Err(anyhow!("missing 'name' field"));
        };
        let Some(description) = proto.description else {
            return Err(anyhow!("missing 'description' field"));
@@ -169,8 +164,8 @@ impl TryFrom<ProtoFlag> for Flag {
            let v: Value = proto_value.try_into()?;
            if values.iter().any(|w| v.since == w.since) {
                let msg = match v.since {
                    None => format!("flag {}: multiple default values", id),
                    Some(x) => format!("flag {}: multiple values for since={}", id, x),
                    None => format!("flag {}: multiple default values", name),
                    Some(x) => format!("flag {}: multiple values for since={}", name, x),
                };
                return Err(anyhow!(msg));
            }
@@ -178,13 +173,35 @@ impl TryFrom<ProtoFlag> for Flag {
        }
        values.sort_by_key(|v| v.since);

        Ok(Flag { id, description, values })
        Ok(Flag { name, description, values })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Namespace {
    pub namespace: String,
    pub flags: Vec<Flag>,
}

impl Namespace {
    pub fn try_from_text_proto(text_proto: &str) -> Result<Namespace> {
        let proto: ProtoNamespace = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        let Some(namespace) = proto.namespace else {
            return Err(anyhow!("missing 'namespace' field"));
        };
        let mut flags = vec![];
        for proto_flag in proto.flag.into_iter() {
            flags.push(proto_flag.try_into()?);
        }
        Ok(Namespace { namespace, flags })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Override {
    pub id: String,
    pub namespace: String,
    pub name: String,
    pub state: FlagState,
    pub permission: Permission,
}
@@ -192,22 +209,25 @@ pub struct Override {
impl Override {
    #[allow(dead_code)] // only used in unit tests
    pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
        let proto: ProtoOverride = crate::protos::try_from_text_proto(text_proto)?;
        let proto: ProtoFlagOverride = crate::protos::try_from_text_proto(text_proto)?;
        proto.try_into()
    }

    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
        let proto: ProtoOverrideConfig = crate::protos::try_from_text_proto(text_proto)?;
        proto.override_.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
        let proto: ProtoFlagOverrides = crate::protos::try_from_text_proto(text_proto)?;
        proto.flag_override.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
    }
}

impl TryFrom<ProtoOverride> for Override {
impl TryFrom<ProtoFlagOverride> for Override {
    type Error = Error;

    fn try_from(proto: ProtoOverride) -> Result<Self, Self::Error> {
        let Some(id) = proto.id else {
            return Err(anyhow!("missing 'id' field"));
    fn try_from(proto: ProtoFlagOverride) -> Result<Self, Self::Error> {
        let Some(namespace) = proto.namespace else {
            return Err(anyhow!("missing 'namespace' field"));
        };
        let Some(name) = proto.name else {
            return Err(anyhow!("missing 'name' field"));
        };
        let Some(proto_state) = proto.state else {
            return Err(anyhow!("missing 'state' field"));
@@ -217,41 +237,42 @@ impl TryFrom<ProtoOverride> for Override {
            return Err(anyhow!("missing 'permission' field"));
        };
        let permission = proto_permission.try_into()?;
        Ok(Override { id, state, permission })
        Ok(Override { namespace, name, state, permission })
    }
}

impl From<Cache> for ProtoDump {
impl From<Cache> for ProtoParsedFlags {
    fn from(cache: Cache) -> Self {
        let mut dump = ProtoDump::new();
        let mut proto = ProtoParsedFlags::new();
        for item in cache.into_iter() {
            dump.item.push(item.into());
            proto.parsed_flag.push(item.into());
        }
        dump
        proto
    }
}

impl From<Item> for ProtoDumpItem {
impl From<Item> for ProtoParsedFlag {
    fn from(item: Item) -> Self {
        let mut dump_item = crate::protos::ProtoDumpItem::new();
        dump_item.set_id(item.id.clone());
        dump_item.set_description(item.description.clone());
        dump_item.set_state(item.state.into());
        dump_item.set_permission(item.permission.into());
        let mut proto = crate::protos::ProtoParsedFlag::new();
        proto.set_namespace(item.namespace.to_owned());
        proto.set_name(item.name.clone());
        proto.set_description(item.description.clone());
        proto.set_state(item.state.into());
        proto.set_permission(item.permission.into());
        for trace in item.trace.into_iter() {
            dump_item.trace.push(trace.into());
            proto.trace.push(trace.into());
        }
        dump_item
        proto
    }
}

impl From<TracePoint> for ProtoDumpTracePoint {
    fn from(tracepoint: TracePoint) -> Self {
        let mut dump_tracepoint = ProtoDumpTracePoint::new();
        dump_tracepoint.set_source(format!("{}", tracepoint.source));
        dump_tracepoint.set_state(tracepoint.state.into());
        dump_tracepoint.set_permission(tracepoint.permission.into());
        dump_tracepoint
impl From<Tracepoint> for ProtoTracepoint {
    fn from(tracepoint: Tracepoint) -> Self {
        let mut proto = ProtoTracepoint::new();
        proto.set_source(format!("{}", tracepoint.source));
        proto.set_state(tracepoint.state.into());
        proto.set_permission(tracepoint.permission.into());
        proto
    }
}

@@ -262,7 +283,7 @@ mod tests {
    #[test]
    fn test_flag_try_from_text_proto() {
        let expected = Flag {
            id: "1234".to_owned(),
            name: "1234".to_owned(),
            description: "Description of the flag".to_owned(),
            values: vec![
                Value::default(FlagState::Disabled, Permission::ReadOnly),
@@ -271,7 +292,7 @@ mod tests {
        };

        let s = r#"
        id: "1234"
        name: "1234"
        description: "Description of the flag"
        value {
            state: DISABLED
@@ -291,7 +312,7 @@ mod tests {
    #[test]
    fn test_flag_try_from_text_proto_bad_input() {
        let s = r#"
        id: "a"
        name: "a"
        description: "Description of the flag"
        "#;
        let error = Flag::try_from_text_proto(s).unwrap_err();
@@ -308,7 +329,7 @@ mod tests {
        assert!(format!("{:?}", error).contains("Message not initialized"));

        let s = r#"
        id: "a"
        name: "a"
        description: "Description of the flag"
        value {
            state: ENABLED
@@ -324,23 +345,27 @@ mod tests {
    }

    #[test]
    fn test_flag_try_from_text_proto_list() {
        let expected = vec![
    fn test_namespace_try_from_text_proto() {
        let expected = Namespace {
            namespace: "ns".to_owned(),
            flags: vec![
                Flag {
                id: "a".to_owned(),
                    name: "a".to_owned(),
                    description: "A".to_owned(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
                Flag {
                id: "b".to_owned(),
                    name: "b".to_owned(),
                    description: "B".to_owned(),
                    values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
                },
        ];
            ],
        };

        let s = r#"
        namespace: "ns"
        flag {
            id: "a"
            name: "a"
            description: "A"
            value {
                state: ENABLED
@@ -348,7 +373,7 @@ mod tests {
            }
        }
        flag {
            id: "b"
            name: "b"
            description: "B"
            value {
                state: DISABLED
@@ -356,7 +381,7 @@ mod tests {
            }
        }
        "#;
        let actual = Flag::try_from_text_proto_list(s).unwrap();
        let actual = Namespace::try_from_text_proto(s).unwrap();

        assert_eq!(expected, actual);
    }
@@ -364,13 +389,15 @@ mod tests {
    #[test]
    fn test_override_try_from_text_proto_list() {
        let expected = Override {
            id: "1234".to_owned(),
            namespace: "ns".to_owned(),
            name: "1234".to_owned(),
            state: FlagState::Enabled,
            permission: Permission::ReadOnly,
        };

        let s = r#"
        id: "1234"
        namespace: "ns"
        name: "1234"
        state: ENABLED
        permission: READ_ONLY
        "#;
@@ -382,7 +409,7 @@ mod tests {
    #[test]
    fn test_flag_resolve() {
        let flag = Flag {
            id: "a".to_owned(),
            name: "a".to_owned(),
            description: "A".to_owned(),
            values: vec![
                Value::default(FlagState::Disabled, Permission::ReadOnly),
+50 −22
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ use crate::aconfig::{Flag, FlagState, Override, Permission};
use crate::commands::Source;

#[derive(Serialize, Deserialize, Debug)]
pub struct TracePoint {
pub struct Tracepoint {
    pub source: Source,
    pub state: FlagState,
    pub permission: Permission,
@@ -30,22 +30,28 @@ pub struct TracePoint {

#[derive(Serialize, Deserialize, Debug)]
pub struct Item {
    pub id: String,
    // TODO: duplicating the Cache.namespace as Item.namespace makes the internal representation
    // closer to the proto message `parsed_flag`; hopefully this will enable us to replace the Item
    // struct and use a newtype instead once aconfig has matured. Until then, namespace should
    // really be a Cow<String>.
    pub namespace: String,
    pub name: String,
    pub description: String,
    pub state: FlagState,
    pub permission: Permission,
    pub trace: Vec<TracePoint>,
    pub trace: Vec<Tracepoint>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Cache {
    build_id: u32,
    namespace: String,
    items: Vec<Item>,
}

impl Cache {
    pub fn new(build_id: u32) -> Cache {
        Cache { build_id, items: vec![] }
    pub fn new(build_id: u32, namespace: String) -> Cache {
        Cache { build_id, namespace, items: vec![] }
    }

    pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -57,31 +63,36 @@ impl Cache {
    }

    pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
        if self.items.iter().any(|item| item.id == flag.id) {
        if self.items.iter().any(|item| item.name == flag.name) {
            return Err(anyhow!(
                "failed to add flag {} from {}: flag already defined",
                flag.id,
                flag.name,
                source,
            ));
        }
        let (state, permission) = flag.resolve(self.build_id);
        self.items.push(Item {
            id: flag.id.clone(),
            namespace: self.namespace.clone(),
            name: flag.name.clone(),
            description: flag.description,
            state,
            permission,
            trace: vec![TracePoint { source, state, permission }],
            trace: vec![Tracepoint { source, state, permission }],
        });
        Ok(())
    }

    pub fn add_override(&mut self, source: Source, override_: Override) -> Result<()> {
        let Some(existing_item) = self.items.iter_mut().find(|item| item.id == override_.id) else {
            return Err(anyhow!("failed to override flag {}: unknown flag", override_.id));
        if override_.namespace != self.namespace {
            // TODO: print warning?
            return Ok(());
        }
        let Some(existing_item) = self.items.iter_mut().find(|item| item.name == override_.name) else {
            return Err(anyhow!("failed to override flag {}: unknown flag", override_.name));
        };
        existing_item.state = override_.state;
        existing_item.permission = override_.permission;
        existing_item.trace.push(TracePoint {
        existing_item.trace.push(Tracepoint {
            source,
            state: override_.state,
            permission: override_.permission,
@@ -105,12 +116,12 @@ mod tests {

    #[test]
    fn test_add_flag() {
        let mut cache = Cache::new(1);
        let mut cache = Cache::new(1, "ns".to_string());
        cache
            .add_flag(
                Source::File("first.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
@@ -120,7 +131,7 @@ mod tests {
            .add_flag(
                Source::File("second.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Disabled, Permission::ReadOnly)],
                },
@@ -134,17 +145,18 @@ mod tests {

    #[test]
    fn test_add_override() {
        fn check(cache: &Cache, id: &str, expected: (FlagState, Permission)) -> bool {
            let item = cache.iter().find(|&item| item.id == id).unwrap();
        fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
            let item = cache.iter().find(|&item| item.name == name).unwrap();
            item.state == expected.0 && item.permission == expected.1
        }

        let mut cache = Cache::new(1);
        let mut cache = Cache::new(1, "ns".to_string());
        let error = cache
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadOnly,
                },
@@ -156,7 +168,7 @@ mod tests {
            .add_flag(
                Source::File("first.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
@@ -169,7 +181,8 @@ mod tests {
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Disabled,
                    permission: Permission::ReadWrite,
                },
@@ -182,12 +195,27 @@ mod tests {
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadWrite,
                },
            )
            .unwrap();
        assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));

        // different namespace -> no-op
        cache
            .add_override(
                Source::Memory,
                Override {
                    namespace: "some-other-namespace".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadOnly,
                },
            )
            .unwrap();
        assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
    }
}
+46 −18

File changed.

Preview size limit exceeded, changes collapsed.

+3 −1
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ fn cli() -> Command {
                        .value_parser(clap::value_parser!(u32))
                        .required(true),
                )
                .arg(Arg::new("namespace").long("namespace").required(true))
                .arg(Arg::new("aconfig").long("aconfig").action(ArgAction::Append))
                .arg(Arg::new("override").long("override").action(ArgAction::Append))
                .arg(Arg::new("cache").long("cache").required(true)),
@@ -72,9 +73,10 @@ fn main() -> Result<()> {
    match matches.subcommand() {
        Some(("create-cache", sub_matches)) => {
            let build_id = *sub_matches.get_one::<u32>("build-id").unwrap();
            let namespace = sub_matches.get_one::<String>("namespace").unwrap();
            let aconfigs = open_zero_or_more_files(sub_matches, "aconfig")?;
            let overrides = open_zero_or_more_files(sub_matches, "override")?;
            let cache = commands::create_cache(build_id, aconfigs, overrides)?;
            let cache = commands::create_cache(build_id, namespace, aconfigs, overrides)?;
            let path = sub_matches.get_one::<String>("cache").unwrap();
            let file = fs::File::create(path)?;
            cache.write_to_writer(file)?;
Loading