diff --git a/Cargo.toml b/Cargo.toml index efc3bc36781382788b8702ebdeaa2825494a6417..650d3911048c356e6ee1378a53391c7bc1da5c9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "lib/tools/documents", "lib/tools/json-pest-parser", "lib/tools/network-documents", + "lib/tools/rules-engine", "lib/tests-tools/crypto-tests-tools", "lib/tests-tools/documents-tests-tools", "lib/tests-tools/rust-tests-tools", diff --git a/lib/tools/rules-engine/Cargo.toml b/lib/tools/rules-engine/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a206cdd485eff0a5813395e21cea783bb0671235 --- /dev/null +++ b/lib/tools/rules-engine/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rules-engine" +version = "0.1.0" +authors = ["librelois <elois@ifee.fr>"] +description = "Rules engine" +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +failure = "0.1.5" +rayon = "1.0.3" + +[dev-dependencies] +maplit = "1.0.1" diff --git a/lib/tools/rules-engine/src/lib.rs b/lib/tools/rules-engine/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..800711925a897ea2a5577ee33aa3c1d93240cae9 --- /dev/null +++ b/lib/tools/rules-engine/src/lib.rs @@ -0,0 +1,479 @@ +// Copyright (C) 2019 Éloïs SANCHEZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Rules engine + +#![deny( + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unsafe_code, + unstable_features, + unused_import_braces +)] + +pub mod rule; + +use failure::Fail; +use rayon::prelude::*; +use rule::{Rule, RuleError, RuleNumber}; +use std::collections::BTreeMap; +use std::fmt::Debug; + +#[derive(Copy, Clone, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)] +pub struct ProtocolVersion(pub usize); + +impl std::fmt::Display for ProtocolVersion { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProtocolRules(pub Vec<RulesGroup>); + +impl From<Vec<usize>> for ProtocolRules { + fn from(rules_numbers: Vec<usize>) -> Self { + ProtocolRules(vec![RulesGroup::Ser( + rules_numbers.into_iter().map(RuleNumber).collect(), + )]) + } +} + +impl From<Vec<RulesGroup>> for ProtocolRules { + fn from(rules_groups: Vec<RulesGroup>) -> Self { + ProtocolRules(rules_groups) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Protocol +pub struct Protocol(BTreeMap<ProtocolVersion, ProtocolRules>); + +impl Protocol { + /// Create new protocol + /// protocol_versions: Dictionary of rules to be applied for each version of the protocol (rules will be applied in the order provided) + pub fn new(protocol_versions: BTreeMap<ProtocolVersion, ProtocolRules>) -> Self { + Protocol(protocol_versions) + } + + /// Get specific protocol version + pub fn get(&self, protocol_version: ProtocolVersion) -> Option<&ProtocolRules> { + self.0.get(&protocol_version) + } +} + +/// Rules groups +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RulesGroup { + /// In serial + Ser(Vec<RuleNumber>), + /// In parallel + Par(Vec<RulesGroup>), +} + +impl RulesGroup { + #[inline] + /// Create singleton rules group + pub fn s1(rule_number: usize) -> Self { + RulesGroup::Ser(vec![RuleNumber(rule_number)]) + } + #[inline] + /// Create parallel set of rules + pub fn pr(rules_numbers: Vec<usize>) -> Self { + RulesGroup::Par(rules_numbers.into_iter().map(RulesGroup::s1).collect()) + } +} + +/// Rules engine +pub struct RulesEngine<D: Debug + Sync, E: Eq + Fail + PartialEq> { + /// All rules + all_rules: BTreeMap<RuleNumber, Rule<D, E>>, +} + +impl<D: Debug + Sync, E: Eq + Fail + PartialEq> RulesEngine<D, E> { + /// Create new rules engine + pub fn new(all_rules: BTreeMap<RuleNumber, Rule<D, E>>) -> Self { + RulesEngine { all_rules } + } + + fn apply_rules_group_ref( + &self, + protocol_version: ProtocolVersion, + rules_group: RulesGroup, + rule_datas: &D, + ) -> Result<(), EngineError<E>> { + match rules_group { + RulesGroup::Ser(rules_numbers) => rules_numbers + .into_iter() + .map(|rule_number| self.apply_rule_ref(protocol_version, rule_number, rule_datas)) + .collect(), + RulesGroup::Par(rules_group) => rules_group + .into_par_iter() + .map(|rg| self.apply_rules_group_ref(protocol_version, rg, rule_datas)) + .collect(), + } + } + + fn apply_rule_ref( + &self, + protocol_version: ProtocolVersion, + rule_number: RuleNumber, + rule_datas: &D, + ) -> Result<(), EngineError<E>> { + if let Some(rule) = self.all_rules.get(&rule_number) { + rule.execute(protocol_version, rule_number, rule_datas) + } else { + Err(EngineError::RuleNotExist { + rule_number, + protocol_version, + }) + } + } + + fn apply_rule_mut( + &self, + protocol_version: ProtocolVersion, + rule_number: RuleNumber, + rule_datas: &mut D, + ) -> Result<(), EngineError<E>> { + if let Some(rule) = self.all_rules.get(&rule_number) { + rule.execute_mut(protocol_version, rule_number, rule_datas) + } else { + Err(EngineError::RuleNotExist { + rule_number, + protocol_version, + }) + } + } + + /// Apply a specific version of the protocol + pub fn apply_protocol( + &self, + protocol: Protocol, + protocol_version: ProtocolVersion, + rule_datas: &mut D, + ) -> Result<(), EngineError<E>> { + if let Some(protocol_rules) = protocol.get(protocol_version) { + for rules_group in &protocol_rules.0 { + let result: Result<(), EngineError<E>> = match rules_group { + RulesGroup::Ser(rules_numbers) => rules_numbers + .iter() + .map(|rule_number| { + self.apply_rule_mut(protocol_version, *rule_number, rule_datas) + }) + .collect(), + RulesGroup::Par(rules_group) => rules_group + .par_iter() + .map(|rg| { + self.apply_rules_group_ref(protocol_version, rg.clone(), rule_datas) + }) + .collect(), + }; + if let Err(err) = result { + return Err(err); + } + } + + Ok(()) + } else { + Err(EngineError::ProtocolVersionNotExist { protocol_version }) + } + } +} + +/// Protocol error +#[derive(Debug, Eq, Fail, PartialEq)] +pub enum EngineError<E: Eq + Fail + PartialEq> { + #[fail(display = "{}", _0)] + /// Rule Error + RuleError(RuleError<E>), + #[fail(display = "protocol V{} not exist", protocol_version)] + /// The protocol version does not exist + ProtocolVersionNotExist { + /// Protocole version + protocol_version: ProtocolVersion, + }, + #[fail( + display = "Rule n°{} not exist (require by protocol V{})", + rule_number, protocol_version + )] + /// A rule required by the protocol version does not exist + RuleNotExist { + /// Rule number + rule_number: RuleNumber, + /// Protocole version + protocol_version: ProtocolVersion, + }, + #[fail( + display = "Rule n°{} is mutable and called in parallel in the V{} protocol, this is prohibited. + A rule can be mutable or called in parallel but not both at the same time.", + rule_number, protocol_version + )] + /// Calling a mutable rule in a part executed in parallel + MutRuleInPar { + /// Rule number + rule_number: RuleNumber, + /// Protocole version + protocol_version: ProtocolVersion, + }, + #[fail( + display = "Rule n°{} does not exist in a version less than or equal to the protocol version (V{})", + rule_number, protocol_version + )] + /// Calling a rule too recent + RuleTooRecent { + /// Rule number + rule_number: RuleNumber, + /// Protocole version + protocol_version: ProtocolVersion, + }, +} + +#[cfg(test)] +mod tests { + + use super::rule::*; + use super::*; + use maplit::btreemap; + + #[derive(Debug)] + struct Datas { + i: usize, + } + + #[derive(Debug, Eq, Fail, PartialEq)] + #[fail(display = "")] + struct Error {} + + fn r2_v1(datas: &mut Datas) -> Result<(), Error> { + if datas.i == 0 { + datas.i += 1; + Ok(()) + } else { + Err(Error {}) + } + } + + fn r3_v2(datas: &Datas) -> Result<(), Error> { + if datas.i == 1 { + Ok(()) + } else { + Err(Error {}) + } + } + + fn get_test_engine() -> RulesEngine<Datas, Error> { + let all_rules: BTreeMap<RuleNumber, Rule<Datas, Error>> = btreemap![ + RuleNumber(2) => Rule::new(RuleNumber(2), btreemap![ + ProtocolVersion(1) => RuleFn::RefMut(r2_v1), + ]).expect("Fail to create rule n°2"), + RuleNumber(3) => Rule::new(RuleNumber(3), btreemap![ + ProtocolVersion(2) => RuleFn::Ref(r3_v2), + ]).expect("Fail to create rule n°2"), + ]; + + RulesEngine::new(all_rules) + } + + #[test] + fn rule_without_impl() { + if let Err(err) = Rule::<Datas, Error>::new(RuleNumber(1), btreemap![]) { + assert_eq!( + RuleWithoutImpl { + rule_number: RuleNumber(1), + }, + err, + ) + } else { + panic!("Rule creation must be fail") + } + + println!("{}", ProtocolVersion(1)); + println!("{}", RuleNumber(1)); + } + + #[test] + fn protocol_empty() -> Result<(), EngineError<Error>> { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol_empty: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => Vec::<usize>::with_capacity(0).into() + ]); + + engine.apply_protocol(protocol_empty, ProtocolVersion(1), &mut datas) + } + + #[test] + fn protocol_version_not_exist() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol_empty: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => Vec::<usize>::with_capacity(0).into() + ]); + + assert_eq!( + Err(EngineError::ProtocolVersionNotExist { + protocol_version: ProtocolVersion(2), + }), + engine.apply_protocol(protocol_empty, ProtocolVersion(2), &mut datas) + ) + } + + #[test] + fn rule_not_exist() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => vec![1usize].into() + ]); + + assert_eq!( + Err(EngineError::RuleNotExist { + rule_number: RuleNumber(1), + protocol_version: ProtocolVersion(1) + }), + engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas) + ); + + let mut datas = Datas { i: 0 }; + + let protocol_par: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => vec![RulesGroup::pr(vec![1usize])].into() + ]); + + assert_eq!( + Err(EngineError::RuleNotExist { + rule_number: RuleNumber(1), + protocol_version: ProtocolVersion(1) + }), + engine.apply_protocol(protocol_par, ProtocolVersion(1), &mut datas) + ); + } + + #[test] + fn rule_fail() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 1 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => vec![2usize].into() + ]); + + assert_eq!( + Err(EngineError::RuleError(RuleError { + rule_number: RuleNumber(2), + cause: Error {}, + })), + engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas) + ) + } + + #[test] + fn par_rule_fail() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(2) => vec![RulesGroup::pr(vec![3usize])].into() + ]); + + assert_eq!( + Err(EngineError::RuleError(RuleError { + rule_number: RuleNumber(3), + cause: Error {}, + })), + engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas) + ) + } + + #[test] + fn rule_too_recent() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => vec![2usize, 3].into() + ]); + + assert_eq!( + Err(EngineError::RuleTooRecent { + protocol_version: ProtocolVersion(1), + rule_number: RuleNumber(3), + }), + engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas) + ) + } + + #[test] + fn par_rule_too_recent() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(1) => vec![RulesGroup::pr(vec![3])].into() + ]); + + assert_eq!( + Err(EngineError::RuleTooRecent { + protocol_version: ProtocolVersion(1), + rule_number: RuleNumber(3), + }), + engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas) + ) + } + + #[test] + fn mut_rule_in_par_protocol() { + let engine = get_test_engine(); + + let mut datas = Datas { i: 1 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(2) => vec![RulesGroup::pr(vec![2usize, 3])].into() + ]); + + assert_eq!( + Err(EngineError::MutRuleInPar { + protocol_version: ProtocolVersion(2), + rule_number: RuleNumber(2), + }), + engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas) + ) + } + + #[test] + fn protocol_success() -> Result<(), EngineError<Error>> { + let engine = get_test_engine(); + + let mut datas = Datas { i: 0 }; + + let protocol: Protocol = Protocol::new(btreemap![ + ProtocolVersion(2) => vec![2usize, 3].into() + ]); + + engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas) + } +} diff --git a/lib/tools/rules-engine/src/rule.rs b/lib/tools/rules-engine/src/rule.rs new file mode 100644 index 0000000000000000000000000000000000000000..62dfd220f3ad79c159a707da72c3de51fe84267f --- /dev/null +++ b/lib/tools/rules-engine/src/rule.rs @@ -0,0 +1,138 @@ +// Copyright (C) 2019 Éloïs SANCHEZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Rules engine : rules + +use crate::{EngineError, ProtocolVersion}; +use failure::Fail; +use std::collections::BTreeMap; +use std::fmt::Debug; + +#[derive(Copy, Clone, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)] +/// Rule number +pub struct RuleNumber(pub usize); + +impl std::fmt::Display for RuleNumber { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Rule error +#[derive(Debug, Eq, Fail, PartialEq)] +#[fail(display = "An error occurred with rule n°{} : {}", rule_number, cause)] +pub struct RuleError<E: Eq + Fail + PartialEq> { + /// Rule number + pub rule_number: RuleNumber, + /// Cause of the error + pub cause: E, +} + +/// Rule immutable execution function +pub type RuleFnRef<D, E> = fn(&D) -> Result<(), E>; + +/// Rule mutable execution function +pub type RuleFnRefMut<D, E> = fn(&mut D) -> Result<(), E>; + +/// Rule execution function +pub enum RuleFn<D, E> { + Ref(RuleFnRef<D, E>), + RefMut(RuleFnRefMut<D, E>), +} + +#[derive(Debug, Copy, Clone, Eq, Fail, PartialEq)] +#[fail( + display = "Fatal error: rules-engine: try to create rule n°{} without implementation !", + rule_number +)] +pub struct RuleWithoutImpl { + pub rule_number: RuleNumber, +} + +/// Rule +pub struct Rule<D: Debug, E: Eq + Fail + PartialEq> { + /// Dictionary of the different versions of the rule execution function + rule_versions: BTreeMap<ProtocolVersion, RuleFn<D, E>>, +} + +impl<D: Debug, E: Eq + Fail + PartialEq> Rule<D, E> { + /// Create new rule + pub fn new( + rule_number: RuleNumber, + rule_versions: BTreeMap<ProtocolVersion, RuleFn<D, E>>, + ) -> Result<Self, RuleWithoutImpl> { + if rule_versions.is_empty() { + Err(RuleWithoutImpl { rule_number }) + } else { + Ok(Rule { rule_versions }) + } + } + /// Executes the correct version of the rule + pub fn execute( + &self, + protocol_version: ProtocolVersion, + rule_number: RuleNumber, + rule_datas: &D, + ) -> Result<(), EngineError<E>> { + let rule_opt: Option<(&ProtocolVersion, &RuleFn<D, E>)> = + self.rule_versions.range(..=protocol_version).last(); + if let Some((_, rule_fn)) = rule_opt { + match rule_fn { + RuleFn::Ref(rule_fn_ref) => rule_fn_ref(rule_datas).map_err(|err| { + EngineError::RuleError(RuleError { + rule_number, + cause: err, + }) + }), + RuleFn::RefMut(_) => Err(EngineError::MutRuleInPar { + rule_number, + protocol_version, + }), + } + } else { + Err(EngineError::RuleTooRecent { + rule_number, + protocol_version, + }) + } + } + /// Executes the correct version of the rule + pub fn execute_mut( + &self, + protocol_version: ProtocolVersion, + rule_number: RuleNumber, + rule_datas: &mut D, + ) -> Result<(), EngineError<E>> { + let rule_opt: Option<(&ProtocolVersion, &RuleFn<D, E>)> = + self.rule_versions.range(..=protocol_version).last(); + if let Some((_, rule_fn)) = rule_opt { + match rule_fn { + RuleFn::Ref(rule_fn_ref) => rule_fn_ref(rule_datas), + RuleFn::RefMut(rule_fn_ref_mut) => rule_fn_ref_mut(rule_datas), + } + .map_err(|err| { + EngineError::RuleError(RuleError { + rule_number, + cause: err, + }) + }) + } else { + Err(EngineError::RuleTooRecent { + rule_number, + protocol_version, + }) + } + } +}