blob: 3aada69967c557bfd9ce1b6e53ed4e616b91d6ae [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use cm_json::{self, cm, Error};
use fidl_fuchsia_data as fd;
use fidl_fuchsia_sys2::{
CapabilityType, ChildDecl, ComponentDecl, ExposeDecl, OfferDecl, OfferTarget, Relation,
RelativeId, UseDecl,
};
use serde_json::{Map, Value};
// Read in a CML file and produce the equivalent FIDL.
pub fn translate(buffer: &str) -> Result<ComponentDecl, Error> {
let json = cm_json::from_json_str(&buffer)?;
cm_json::validate_json(&json, cm_json::CM_SCHEMA)?;
let document: cm::Document = serde_json::from_str(&buffer)
.map_err(|e| Error::parse(format!("Couldn't read input as struct: {}", e)))?;
translate_cm(document)
}
fn translate_cm(document: cm::Document) -> Result<ComponentDecl, Error> {
// We can't deserialize into a ComponentDecl fidl directly because the autogenerated rust
// bindings do not contain the necessary attributes to deserialize correctly. Also, the
// deserialization of freeform objects to dictionaries is not trivial.
let mut out = new_component_decl();
if let Some(program) = document.program {
out.program = Some(dictionary_from_map(program)?);
}
if let Some(uses) = document.uses {
out.uses = Some(translate_uses(uses)?);
}
if let Some(exposes) = document.exposes {
out.exposes = Some(translate_exposes(exposes)?);
}
if let Some(offers) = document.offers {
out.offers = Some(translate_offers(offers)?);
}
if let Some(children) = document.children {
out.children = Some(translate_children(children)?);
}
if let Some(facets) = document.facets {
out.facets = Some(dictionary_from_map(facets)?);
}
Ok(out)
}
fn translate_uses(use_in: Vec<cm::Use>) -> Result<Vec<UseDecl>, Error> {
let mut out_uses = vec![];
for use_ in use_in {
let type_ = capability_from_str(&use_.r#type)?;
out_uses.push(UseDecl {
type_: Some(type_),
source_path: Some(use_.source_path),
target_path: Some(use_.target_path),
});
}
Ok(out_uses)
}
fn translate_exposes(expose_in: Vec<cm::Expose>) -> Result<Vec<ExposeDecl>, Error> {
let mut out_exposes = vec![];
for expose in expose_in {
let type_ = capability_from_str(&expose.r#type)?;
let source = RelativeId {
relation: Some(relation_from_str(&expose.source.relation)?),
child_name: expose.source.child_name,
};
out_exposes.push(ExposeDecl {
type_: Some(type_),
source_path: Some(expose.source_path),
source: Some(source),
target_path: Some(expose.target_path),
});
}
Ok(out_exposes)
}
fn translate_offers(offer_in: Vec<cm::Offer>) -> Result<Vec<OfferDecl>, Error> {
let mut out_offers = vec![];
for offer in offer_in {
let type_ = capability_from_str(&offer.r#type)?;
let source = RelativeId {
relation: Some(relation_from_str(&offer.source.relation)?),
child_name: offer.source.child_name,
};
let mut out_targets = vec![];
for target in offer.targets {
out_targets.push(OfferTarget {
target_path: Some(target.target_path),
child_name: Some(target.child_name),
});
}
out_offers.push(OfferDecl {
type_: Some(type_),
source_path: Some(offer.source_path),
source: Some(source),
targets: Some(out_targets),
});
}
Ok(out_offers)
}
fn translate_children(children_in: Vec<cm::Child>) -> Result<Vec<ChildDecl>, Error> {
let mut out_children = vec![];
for child in children_in {
out_children.push(ChildDecl {
name: Some(child.name),
uri: Some(child.uri),
});
}
Ok(out_children)
}
fn dictionary_from_map(in_obj: Map<String, serde_json::Value>) -> Result<fd::Dictionary, Error> {
let mut dict = fd::Dictionary { entries: vec![] };
do_dictionary_from_map(in_obj, &mut dict)?;
Ok(dict)
}
fn do_dictionary_from_map(
in_obj: Map<String, Value>, out: &mut fd::Dictionary,
) -> Result<(), Error> {
for (k, v) in in_obj {
if let Some(value) = convert_value(v)? {
out.entries.push(fd::Entry {
key: k,
value: Some(value),
});
}
}
Ok(())
}
fn convert_value(v: Value) -> Result<Option<Box<fd::Value>>, Error> {
Ok(match v {
Value::Null => None,
Value::Bool(b) => Some(Box::new(fd::Value::Bit(b))),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Some(Box::new(fd::Value::Inum(i)))
} else if let Some(f) = n.as_f64() {
Some(Box::new(fd::Value::Fnum(f)))
} else {
return Err(Error::Parse(format!("Number is out of range: {}", n)));
}
}
Value::String(s) => Some(Box::new(fd::Value::Str(s.clone()))),
Value::Array(a) => {
let mut values = vec![];
for v in a {
if let Some(value) = convert_value(v)? {
values.push(Some(value));
}
}
let vector = fd::Vector { values };
Some(Box::new(fd::Value::Vec(vector)))
}
Value::Object(o) => {
let mut dict = fd::Dictionary { entries: vec![] };
do_dictionary_from_map(o, &mut dict)?;
Some(Box::new(fd::Value::Dict(dict)))
}
})
}
fn capability_from_str(value: &str) -> Result<CapabilityType, Error> {
match value {
cm::SERVICE => Ok(CapabilityType::Service),
cm::DIRECTORY => Ok(CapabilityType::Directory),
_ => Err(Error::parse(format!("Unknown capability type: {}", value))),
}
}
fn relation_from_str(value: &str) -> Result<Relation, Error> {
match value {
cm::REALM => Ok(Relation::Realm),
cm::SELF => Ok(Relation::Myself),
cm::CHILD => Ok(Relation::Child),
_ => Err(Error::parse(format!("Unknown relation: {}", value))),
}
}
fn new_component_decl() -> ComponentDecl {
ComponentDecl {
program: None,
uses: None,
exposes: None,
offers: None,
facets: None,
children: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn translate_test(input: serde_json::value::Value, expected_output: ComponentDecl) {
let component_decl = translate(&format!("{}", input)).expect("translation failed");
assert_eq!(component_decl, expected_output);
}
macro_rules! test_translate {
(
$(
$test_name:ident => {
input = $input:expr,
output = $result:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
translate_test($input, $result);
}
)+
}
}
#[test]
fn test_translate_invalid_cm_fails() {
let input = json!({
"exposes": [
{
"type": "nothing",
"source_path": "/svc/fuchsia.logger.Log",
"source": {
"relation": "self"
},
"target_path": "/svc/fuchsia.logger.Log"
}
]
});
let expected_res: Result<ComponentDecl, Error> = Err(Error::parse(
"Pattern condition is not met at /exposes/0/type",
));
let res = translate(&format!("{}", input));
assert_eq!(format!("{:?}", res), format!("{:?}", expected_res));
}
#[test]
test_translate! {
test_translate_empty => {
input = json!({}),
output = new_component_decl(),
},
test_translate_program => {
input = json!({
"program": {
"binary": "bin/app"
}
}),
output = {
let program = fd::Dictionary{entries: vec![
fd::Entry{
key: "binary".to_string(),
value: Some(Box::new(fd::Value::Str("bin/app".to_string()))),
}
]};
let mut decl = new_component_decl();
decl.program = Some(program);
decl
},
},
test_translate_dictionary_primitive => {
input = json!({
"program": {
"string": "bar",
"int": -42,
"float": 3.14,
"bool": true,
"ignore": null
}
}),
output = {
let program = fd::Dictionary{entries: vec![
fd::Entry{
key: "bool".to_string(),
value: Some(Box::new(fd::Value::Bit(true))),
},
fd::Entry{
key: "float".to_string(),
value: Some(Box::new(fd::Value::Fnum(3.14))),
},
fd::Entry{
key: "int".to_string(),
value: Some(Box::new(fd::Value::Inum(-42))),
},
fd::Entry{
key: "string".to_string(),
value: Some(Box::new(fd::Value::Str("bar".to_string()))),
},
]};
let mut decl = new_component_decl();
decl.program = Some(program);
decl
},
},
test_translate_dictionary_nested => {
input = json!({
"program": {
"obj": {
"array": [
{
"string": "bar"
},
-42
],
},
"bool": true
}
}),
output = {
let dict_inner = fd::Dictionary{entries: vec![
fd::Entry{
key: "string".to_string(),
value: Some(Box::new(fd::Value::Str("bar".to_string()))),
},
]};
let vector = fd::Vector{values: vec![
Some(Box::new(fd::Value::Dict(dict_inner))),
Some(Box::new(fd::Value::Inum(-42)))
]};
let dict_outer = fd::Dictionary{entries: vec![
fd::Entry{
key: "array".to_string(),
value: Some(Box::new(fd::Value::Vec(vector))),
},
]};
let program = fd::Dictionary{entries: vec![
fd::Entry{
key: "bool".to_string(),
value: Some(Box::new(fd::Value::Bit(true))),
},
fd::Entry{
key: "obj".to_string(),
value: Some(Box::new(fd::Value::Dict(dict_outer))),
},
]};
let mut decl = new_component_decl();
decl.program = Some(program);
decl
},
},
test_translate_uses => {
input = json!({
"uses": [
{
"type": "service",
"source_path": "/fonts/CoolFonts",
"target_path": "/svc/fuchsia.fonts.Provider"
},
{
"type": "directory",
"source_path": "/data/assets",
"target_path": "/data/assets"
}
]
}),
output = {
let uses = vec![
UseDecl{
type_: Some(CapabilityType::Service),
source_path: Some("/fonts/CoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.Provider".to_string()),
},
UseDecl{
type_: Some(CapabilityType::Directory),
source_path: Some("/data/assets".to_string()),
target_path: Some("/data/assets".to_string()),
},
];
let mut decl = new_component_decl();
decl.uses = Some(uses);
decl
},
},
test_translate_exposes => {
input = json!({
"exposes": [
{
"type": "service",
"source_path": "/loggers/fuchsia.logger.Log",
"source": {
"relation": "child",
"child_name": "logger"
},
"target_path": "/svc/fuchsia.logger.Log"
},
{
"type": "directory",
"source_path": "/volumes/blobfs",
"source": {
"relation": "self"
},
"target_path": "/volumes/blobfs"
}
]
}),
output = {
let exposes = vec![
ExposeDecl{
type_: Some(CapabilityType::Service),
source_path: Some("/loggers/fuchsia.logger.Log".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Child),
child_name: Some("logger".to_string()),
}),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
},
ExposeDecl{
type_: Some(CapabilityType::Directory),
source_path: Some("/volumes/blobfs".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Myself),
child_name: None,
}),
target_path: Some("/volumes/blobfs".to_string()),
},
];
let mut decl = new_component_decl();
decl.exposes = Some(exposes);
decl
},
},
test_translate_offers => {
input = json!({
"offers": [
{
"type": "directory",
"source_path": "/data/assets",
"source": {
"relation": "realm"
},
"targets": [
{
"target_path": "/data/realm_assets",
"child_name": "echo2_server"
},
{
"target_path": "/data/assets",
"child_name": "hello_world"
}
]
},
{
"type": "directory",
"source_path": "/data/config",
"source": {
"relation": "self"
},
"targets": [
{
"target_path": "/data/config",
"child_name": "netstack"
}
]
},
{
"type": "service",
"source_path": "/svc/fuchsia.logger.Log",
"source": {
"relation": "child",
"child_name": "logger"
},
"targets": [
{
"target_path": "/svc/fuchsia.logger.SysLog",
"child_name": "echo2_server"
}
]
}
]
}),
output = {
let offers = vec![
OfferDecl{
type_: Some(CapabilityType::Directory),
source_path: Some("/data/assets".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Realm),
child_name: None
}),
targets: Some(vec![
OfferTarget{
target_path: Some("/data/realm_assets".to_string()),
child_name: Some("echo2_server".to_string()),
},
OfferTarget{
target_path: Some("/data/assets".to_string()),
child_name: Some("hello_world".to_string()),
},
]),
},
OfferDecl{
type_: Some(CapabilityType::Directory),
source_path: Some("/data/config".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Myself),
child_name: None
}),
targets: Some(vec![
OfferTarget{
target_path: Some("/data/config".to_string()),
child_name: Some("netstack".to_string()),
},
]),
},
OfferDecl{
type_: Some(CapabilityType::Service),
source_path: Some("/svc/fuchsia.logger.Log".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Child),
child_name: Some("logger".to_string()),
}),
targets: Some(vec![
OfferTarget{
target_path: Some("/svc/fuchsia.logger.SysLog".to_string()),
child_name: Some("echo2_server".to_string()),
},
]),
},
];
let mut decl = new_component_decl();
decl.offers = Some(offers);
decl
},
},
test_translate_children => {
input = json!({
"children": [
{
"name": "logger",
"uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
{
"name": "echo2_server",
"uri": "fuchsia-pkg://fuchsia.com/echo2_server/stable#meta/echo2_server.cm"
}
]
}),
output = {
let children = vec![
ChildDecl{
name: Some("logger".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
},
ChildDecl{
name: Some("echo2_server".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/echo2_server/stable#meta/echo2_server.cm".to_string()),
},
];
let mut decl = new_component_decl();
decl.children = Some(children);
decl
},
},
test_translate_facets => {
input = json!({
"facets": {
"authors": [
"me",
"you"
],
"title": "foo",
"year": 2018
}
}),
output = {
let vector = fd::Vector{values: vec![
Some(Box::new(fd::Value::Str("me".to_string()))),
Some(Box::new(fd::Value::Str("you".to_string()))),
]};
let facets = fd::Dictionary{entries: vec![
fd::Entry{
key: "authors".to_string(),
value: Some(Box::new(fd::Value::Vec(vector))),
},
fd::Entry{
key: "title".to_string(),
value: Some(Box::new(fd::Value::Str("foo".to_string()))),
},
fd::Entry{
key: "year".to_string(),
value: Some(Box::new(fd::Value::Inum(2018))),
},
]};
let mut decl = new_component_decl();
decl.facets = Some(facets);
decl
},
},
test_translate_all_sections => {
input = json!({
"program": {
"binary": "bin/app"
},
"uses": [
{
"type": "service",
"source_path": "/fonts/CoolFonts",
"target_path": "/svc/fuchsia.fonts.Provider"
}
],
"exposes": [
{
"type": "directory",
"source_path": "/volumes/blobfs",
"source": {
"relation": "self"
},
"target_path": "/volumes/blobfs"
}
],
"offers": [
{
"type": "service",
"source_path": "/svc/fuchsia.logger.Log",
"source": {
"relation": "child",
"child_name": "logger"
},
"targets": [
{
"target_path": "/svc/fuchsia.logger.Log",
"child_name": "netstack"
}
]
}
],
"children": [
{
"name": "logger",
"uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
{
"name": "netstack",
"uri": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm"
}
],
"facets": {
"author": "Fuchsia",
"year": 2018
}
}),
output = {
let program = fd::Dictionary{entries: vec![
fd::Entry{
key: "binary".to_string(),
value: Some(Box::new(fd::Value::Str("bin/app".to_string()))),
},
]};
let uses = vec![
UseDecl{
type_: Some(CapabilityType::Service),
source_path: Some("/fonts/CoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.Provider".to_string()),
},
];
let exposes = vec![
ExposeDecl{
type_: Some(CapabilityType::Directory),
source_path: Some("/volumes/blobfs".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Myself),
child_name: None,
}),
target_path: Some("/volumes/blobfs".to_string()),
},
];
let offers = vec![
OfferDecl{
type_: Some(CapabilityType::Service),
source_path: Some("/svc/fuchsia.logger.Log".to_string()),
source: Some(RelativeId{
relation: Some(Relation::Child),
child_name: Some("logger".to_string()),
}),
targets: Some(vec![
OfferTarget{
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
child_name: Some("netstack".to_string()),
},
]),
},
];
let children = vec![
ChildDecl{
name: Some("logger".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
},
ChildDecl{
name: Some("netstack".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
},
];
let facets = fd::Dictionary{entries: vec![
fd::Entry{
key: "author".to_string(),
value: Some(Box::new(fd::Value::Str("Fuchsia".to_string()))),
},
fd::Entry{
key: "year".to_string(),
value: Some(Box::new(fd::Value::Inum(2018))),
},
]};
ComponentDecl{
program: Some(program),
uses: Some(uses),
exposes: Some(exposes),
offers: Some(offers),
children: Some(children),
facets: Some(facets)
}
},
},
}
}