#![warn(unused_extern_crates)]
#![deny(unsafe_code)]
#![allow(clippy::implicit_hasher)]
pub use smartnoise_validator::proto;
use smartnoise_validator::errors::*;
pub mod utilities;
pub mod components;
pub mod base;
use std::collections::{HashMap, HashSet};
use std::vec::Vec;
use smartnoise_validator::base::{Value, ReleaseNode, Release, IndexKey, ComponentExpansion, ValueProperties};
use smartnoise_validator::utilities::{get_sinks, get_input_properties, get_dependents};
use crate::components::Evaluable;
use std::iter::FromIterator;
use indexmap::map::IndexMap;
pub type NodeArguments = IndexMap<IndexKey, Value>;
pub fn release(
privacy_definition: Option<proto::PrivacyDefinition>,
mut computation_graph: HashMap<u32, proto::Component>,
mut release: Release,
filter_level: proto::FilterLevel
) -> Result<(Release, Vec<Error>)> {
if let Some(privacy_definition) = &privacy_definition {
if !cfg!(feature="use-mpfr") && privacy_definition.protect_floating_point {
return Err("runtime has been compiled without mpfr, and floating point protections have been enabled".into())
}
}
let mut traversal: Vec<u32> = get_sinks(&computation_graph).into_iter().collect();
let (mut properties, mut warnings) = smartnoise_validator::get_properties(
privacy_definition.clone(),
computation_graph.clone(),
release.clone(),
release.keys().copied().collect()
)?;
let mut maximum_id = computation_graph.keys().max().cloned().unwrap_or(0);
let original_ids: HashSet<u32> = HashSet::from_iter(release.keys().cloned());
let mut parents = get_dependents(&computation_graph);
while !traversal.is_empty() {
let component_id: u32 = *traversal.last().unwrap();
if release.contains_key(&component_id) {
traversal.pop();
continue;
}
let component: &proto::Component = computation_graph.get(&component_id)
.ok_or_else(|| Error::from("attempted to retrieve a non-existent component id"))?;
let mut evaluable = true;
for source_node_id in component.arguments().values() {
if !release.contains_key(&source_node_id) {
evaluable = false;
traversal.push(*source_node_id);
break;
}
}
if !evaluable {
continue;
}
let node_properties: IndexMap<IndexKey, ValueProperties> =
get_input_properties(&component, &properties)?;
let public_arguments = component.arguments().into_iter()
.map(|(name, node_id)| (name, release.get(&node_id).unwrap()))
.filter(|(_, release_node)| release_node.public)
.map(|(name, release_node)| (name, release_node.clone()))
.collect::<IndexMap<IndexKey, ReleaseNode>>();
let mut expansion: ComponentExpansion = match smartnoise_validator::expand_component(
component.clone(),
node_properties,
public_arguments,
privacy_definition.clone(),
component_id,
maximum_id) {
Ok(expansion) => expansion,
Err(err) => {
warnings.push(err);
let mut descendant_traversal = Vec::new();
let mut descendants = HashSet::new();
descendant_traversal.push(component_id);
while !descendant_traversal.is_empty() {
let descendant = descendant_traversal.pop().unwrap();
if let Some(parents) = parents.get(&descendant) {
parents.iter().for_each(|parent| {
descendant_traversal.push(*parent);
})
}
descendants.insert(descendant);
}
traversal = traversal.into_iter()
.filter(|v| !descendants.contains(v))
.collect();
continue
}
};
maximum_id = expansion.computation_graph.keys()
.max().cloned().unwrap_or(0).max(maximum_id);
computation_graph.extend(expansion.computation_graph);
properties.extend(expansion.properties);
release.extend(expansion.releases);
warnings.extend(expansion.warnings);
if !expansion.traversal.is_empty() {
expansion.traversal.reverse();
traversal.extend(expansion.traversal);
parents = get_dependents(&computation_graph);
continue;
}
traversal.pop();
let component = computation_graph.get(&component_id).unwrap();
let mut node_arguments = IndexMap::<IndexKey, Value>::new();
for (name, argument_node_id) in component.arguments().into_iter() {
if filter_level == proto::FilterLevel::All {
release.get(&argument_node_id)
.map(|v| v.clone().value)
.or_else(|| node_arguments.get(&name).cloned())
.map(|release_node|
node_arguments.insert(name, release_node));
continue
}
let is_orphan = if let Some(parent_node_ids) = parents.get_mut(&argument_node_id) {
parent_node_ids.remove(&component_id);
parent_node_ids.is_empty()
} else {true};
let must_include = filter_level == proto::FilterLevel::PublicAndPrior
&& original_ids.contains(&argument_node_id);
let is_public = release.get(&argument_node_id).map(|v| v.public).unwrap_or(false);
let is_omitted = computation_graph.get(&argument_node_id)
.map(|v| v.omit).unwrap_or(true);
if is_orphan && ((!must_include && !is_public) || is_omitted) {
release.remove(&argument_node_id)
} else {
release.get(&argument_node_id).cloned()
}
.map(|v| v.value)
.or_else(|| node_arguments.get(&name).cloned())
.map(|v| node_arguments.insert(name, v));
}
let mut evaluation = component.variant.as_ref()
.ok_or_else(|| Error::from("variant of component must be known"))?
.evaluate(&privacy_definition, node_arguments)?;
evaluation.public = properties.get(&component_id)
.map(ValueProperties::is_public)
.unwrap_or(false);
release.insert(component_id, evaluation);
}
release.retain(|node_id, _| !computation_graph.get(node_id)
.map(|v| v.omit)
.unwrap_or(true));
match filter_level {
proto::FilterLevel::Public =>
release.retain(|_, release_node|
release_node.public),
proto::FilterLevel::PublicAndPrior =>
release.retain(|node_id, release_node|
release_node.public || original_ids.contains(node_id)),
proto::FilterLevel::All => (),
};
Ok((release, warnings))
}