blob: 45f5ec0c084215cf604f50009382fa8f46cac477 [file] [log] [blame]
/*
* Copyright (C) 2024 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 crate::commands::assign_flag_ids;
use crate::protos::ProtoFlagState;
use crate::storage::{self, FlagPackage};
use anyhow::{anyhow, Result};
#[derive(PartialEq, Debug)]
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
pub file_size: u32,
pub num_flags: u32,
pub boolean_value_offset: u32,
}
impl FlagValueHeader {
fn new(container: &str, num_flags: u32) -> Self {
Self {
version: storage::FILE_VERSION,
container: String::from(container),
file_size: 0,
num_flags,
boolean_value_offset: 0,
}
}
fn as_bytes(&self) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice(&self.version.to_le_bytes());
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_flags.to_le_bytes());
result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
result
}
}
#[derive(PartialEq, Debug)]
pub struct FlagValueList {
pub header: FlagValueHeader,
pub booleans: Vec<bool>,
}
impl FlagValueList {
pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
// create list
let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
let mut list = Self {
header: FlagValueHeader::new(container, num_flags),
booleans: vec![false; num_flags as usize],
};
for pkg in packages.iter() {
let start_offset = pkg.boolean_offset as usize;
let flag_ids = assign_flag_ids(pkg.package_name, pkg.boolean_flags.iter().copied())?;
for pf in pkg.boolean_flags.iter() {
let fid = flag_ids
.get(pf.name())
.ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
list.booleans[start_offset + (*fid as usize)] =
pf.state() == ProtoFlagState::ENABLED;
}
}
// initialize all header fields
list.header.boolean_value_offset = list.header.as_bytes().len() as u32;
list.header.file_size = list.header.boolean_value_offset + num_flags;
Ok(list)
}
pub fn as_bytes(&self) -> Vec<u8> {
[
self.header.as_bytes(),
self.booleans
.iter()
.map(|&v| u8::from(v).to_le_bytes())
.collect::<Vec<_>>()
.concat(),
]
.concat()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{
group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
tests::read_u32_from_bytes, tests::read_u8_from_bytes,
};
impl FlagValueHeader {
// test only method to deserialize back into the header struct
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let mut head = 0;
Ok(Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
})
}
}
impl FlagValueList {
// test only method to deserialize back into the flag value struct
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let header = FlagValueHeader::from_bytes(bytes)?;
let num_flags = header.num_flags;
let mut head = header.as_bytes().len();
let booleans = (0..num_flags)
.map(|_| read_u8_from_bytes(bytes, &mut head).unwrap() == 1)
.collect();
let list = Self { header, booleans };
Ok(list)
}
}
pub fn create_test_flag_value_list() -> Result<FlagValueList> {
let caches = parse_all_test_flags();
let packages = group_flags_by_package(caches.iter());
FlagValueList::new("system", &packages)
}
#[test]
// this test point locks down the flag value creation and each field
fn test_list_contents() {
let flag_value_list = create_test_flag_value_list();
assert!(flag_value_list.is_ok());
let header: &FlagValueHeader = &flag_value_list.as_ref().unwrap().header;
let expected_header = FlagValueHeader {
version: storage::FILE_VERSION,
container: String::from("system"),
file_size: 34,
num_flags: 8,
boolean_value_offset: 26,
};
assert_eq!(header, &expected_header);
let booleans: &Vec<bool> = &flag_value_list.as_ref().unwrap().booleans;
let expected_booleans: Vec<bool> = vec![false; header.num_flags as usize];
assert_eq!(booleans, &expected_booleans);
}
#[test]
// this test point locks down the value list serialization
fn test_serialization() {
let flag_value_list = create_test_flag_value_list().unwrap();
let header: &FlagValueHeader = &flag_value_list.header;
let reinterpreted_header = FlagValueHeader::from_bytes(&header.as_bytes());
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
assert!(reinterpreted_value_list.is_ok());
assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
}
}