mod interpreter

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2023-04-24 11:52:46 +02:00
parent 4688712904
commit 934376e974
4 changed files with 191 additions and 1 deletions

View file

@ -11,6 +11,7 @@ nom = "7.1"
jid = "0.9"
minidom = "0.15.1"
nom_locate = "4.0.0"
rand = "0.8"
# [patch.crates-io]
# jid = { path = "../xmpp-rs/jid" }

View file

@ -201,6 +201,7 @@ impl<'a> PartialEq<Vec<Node>> for ScanNodes<'a, NonStrictComparison> {
/// Comparison between elements needs to take into accounts the `scansion:strict` attribute which
/// changes the way the comparison is done.
/// Also uses the custom ScanNode implementation.
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, Clone)]
pub struct ScanElement<'a> {
elem: Element,

186
src/interpreter.rs Normal file
View file

@ -0,0 +1,186 @@
// Copyright (C) 2023-2099 The crate authors.
//
// 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/>.
use crate::element::ScanElement;
use crate::parsers::parse_spec;
use crate::types::{Action, Context, Entity, Spec};
use jid::Jid;
use minidom::{Element, Error as MinidomError};
use rand::{thread_rng, Rng};
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct InOutStanza<'a> {
pub inbound: Vec<ScanElement<'a>>,
pub outbound: Vec<ScanElement<'a>>,
}
impl<'a> InOutStanza<'a> {
fn new() -> Self {
InOutStanza {
inbound: Vec::new(),
outbound: Vec::new(),
}
}
fn sends(&mut self, scan: ScanElement<'a>) {
self.inbound.push(scan)
}
fn receives(&mut self, scan: ScanElement<'a>) {
self.outbound.push(scan)
}
}
fn make_resource() -> String {
let id: u8 = thread_rng().gen();
format!("{}", id)
}
// We don't need information other than FullJid and name here as we're going to use that directly
// in tests. No need to connect anywhere.
/// When connecting, clients are assigned a FullJid during <bind/>. This function adds a random
/// resource to clients that only have a BareJid specified.
fn bind_context(context: Context) -> Context {
context
.into_iter()
.map(|(name, mut context)| {
match context {
Entity::Client(ref mut client) => match &client.jid {
Jid::Bare(bare) => {
let jid = bare.clone().with_resource(make_resource());
client.jid = Jid::Full(jid)
}
_ => (),
},
};
(name, context)
})
.collect::<Context>()
}
pub fn read_actions<'a>(spec: Spec, context: &'a Context) -> Result<InOutStanza<'a>, MinidomError> {
let mut inout = InOutStanza::new();
for action in spec.actions {
match action {
Action::Receive(name, s) => {
let _ = context
.get(&name)
.expect(format!("Name '{}' not found in clients", name).as_str());
let elem = s.parse::<Element>()?;
inout.receives(ScanElement::new(elem).with_context(Some(&context)));
}
Action::Send(name, s) => {
let _ = context
.get(&name)
.expect(format!("Name '{}' not found in clients", name).as_str());
let elem = s.parse::<Element>()?;
inout.sends(ScanElement::new(elem).with_context(Some(&context)));
}
_ => (),
}
}
Ok(inout)
}
pub fn read_spec<'a>(buf: &str) -> Spec {
let mut spec = parse_spec(buf).unwrap();
spec.context = bind_context(spec.context.clone());
spec
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Client, Entity};
static BUF1: &str = r#"# MUC Creation
# Single user MUC creation
## muc
[Client] louise
jid: louise@localhost
password: password
-----
louise connects
louise sends:
<presence from="${louise's full JID}" to="room@muc.localhost/louise" xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc"/>
</presence>
louise receives:
<presence to="${louise's full JID}" from="room@muc.localhost/louise" id="{scansion:any}" xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<status code="201"/>
<item affiliation="owner" jid="${louise's full JID}" role="moderator"/>
<status code="110"/>
</x>
</presence>
"#;
static JOIN: &str = r#"<presence from="${louise's full JID}" to="room@muc.localhost/louise" xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc"/>
</presence>"#;
static CONFIRM: &str = r#"<presence to="${louise's full JID}" from="room@muc.localhost/louise" id="{scansion:any}" xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<status code="201"/>
<item affiliation="owner" jid="${louise's full JID}" role="moderator"/>
<status code="110"/>
</x>
</presence>"#;
#[test]
fn test_bind_context() {
let spec = parse_spec(BUF1).unwrap();
let context = bind_context(spec.context);
assert_eq!(context.len(), 1);
match context.get("louise") {
Some(Entity::Client(Client {
jid: Jid::Full(full),
password,
..
})) => {
assert_eq!(full.node, Some(String::from("louise")));
assert_eq!(full.domain, String::from("localhost"));
assert_eq!(password, "password");
}
other => panic!("Error: context: {other:?}"),
}
}
#[test]
fn test_read_actions() {
let spec = parse_spec(BUF1.clone()).unwrap();
let context: Context = bind_context(spec.context.clone());
let res = InOutStanza {
inbound: vec![
ScanElement::new(JOIN.parse::<Element>().unwrap()).with_context(Some(&context))
],
outbound: vec![
ScanElement::new(CONFIRM.parse::<Element>().unwrap()).with_context(Some(&context))
],
};
assert_eq!(read_actions(spec, &context).unwrap(), res);
}
}

View file

@ -7,9 +7,11 @@
#![feature(let_chains)]
pub mod element;
pub mod interpreter;
pub mod parsers;
pub mod types;
pub use element::ScanElement;
pub use interpreter::{read_actions, read_spec};
pub use parsers::parse_spec;
pub use types::{Action, Client, Context, Metadata, Name, Spec};
pub use types::{Action, Client, Context, Entity, Metadata, Name, Spec};