Kaynağa Gözat

* Fixed a bug in the way actor definitions were parsed.
* Added a new protocol validator.
* Removed the requirement that servers receive the `Accept` message.

Matthew Carr 1 yıl önce
ebeveyn
işleme
2fef915a40

+ 4 - 8
crates/btfs/src/lib.rs

@@ -14,13 +14,10 @@ impl CallMsg for Open {
 }
 protocol! {
     named FsProtocol;
-    let states = [
-        ServerInit, Listening,
-        Client,
-        FileInit, Opened,
-        FileHandle,
-    ];
-    ServerInit?Activate -> Listening;
+    let server = [Listening];
+    let client = [Client];
+    let file = [FileInit, Opened];
+    let file_handle = [FileHandle];
 
     Client -> Client, >service(Listening)!Query;
     Listening?Query -> Listening, >Client!Query::Reply;
@@ -30,7 +27,6 @@ protocol! {
     Listening?Open -> Listening, FileInit, >Client!Open::Reply[Opened], FileInit!Open;
     Client?Open::Reply[Opened] -> Client, FileHandle[Opened];
 
-    FileInit?Activate -> FileInit;
     FileInit?Open -> Opened;
 
     FileHandle[Opened] -> FileHandle[Opened], >Opened!FileOp;

+ 3 - 9
crates/btproto/src/generation.rs

@@ -1,7 +1,7 @@
 use std::collections::{HashMap, HashSet};
 
 use crate::parsing::{Message, Transition};
-use btrun::{Activate, End};
+use btrun::End;
 
 use super::Protocol;
 use proc_macro2::{Ident, TokenStream};
@@ -61,9 +61,7 @@ impl Message {
     fn type_tokens(&self) -> TokenStream {
         // We generate a fully-qualified path to the Activate message so that it doesn't need to be
         // imported for every protocol definition.
-        let msg_type = if self.msg_type == Activate::ident() {
-            quote! { ::btrun::Activate }
-        } else {
+        let msg_type = {
             let msg_type = &self.msg_type;
             quote! { #msg_type }
         };
@@ -83,11 +81,7 @@ impl Transition {
     /// Generates the tokens for the code which implements this transition.
     fn generate_tokens(&self) -> TokenStream {
         let (msg_arg, method_ident) = if let Some(msg) = self.in_msg() {
-            let msg_type = if msg.msg_type == Activate::ident() {
-                quote! { ::btrun::Activate }
-            } else {
-                msg.msg_type.to_token_stream()
-            };
+            let msg_type = msg.msg_type.to_token_stream();
             let method_ident = format_ident!("handle_{}", msg.ident().pascal_to_snake());
             let msg_arg = quote! { , msg: #msg_type };
             (msg_arg, method_ident)

+ 72 - 31
crates/btproto/src/parsing.rs

@@ -3,8 +3,12 @@
 use proc_macro2::Span;
 use quote::format_ident;
 use syn::{
-    bracketed, parenthesized, parse::Parse, punctuated::Punctuated, spanned::Spanned,
-    token::Bracket, Ident, LitInt, Token,
+    bracketed, parenthesized,
+    parse::{Parse, ParseStream},
+    punctuated::Punctuated,
+    spanned::Spanned,
+    token::Bracket,
+    Ident, LitInt, Token,
 };
 
 /// This type represents the top-level production for the protocol grammar.
@@ -14,8 +18,7 @@ pub(crate) struct Protocol {
     name_def_semi: Token![;],
     #[allow(dead_code)]
     version_def: Option<(VersionDef, Token![;])>,
-    pub(crate) states_def: ActorDef,
-    states_def_semi: Token![;],
+    pub(crate) actor_defs: Punctuated<ActorDef, Token![;]>,
     pub(crate) transitions: Punctuated<Transition, Token![;]>,
 }
 
@@ -23,17 +26,18 @@ pub(crate) struct Protocol {
 impl Protocol {
     pub(crate) fn new(
         name_def: NameDef,
-        states_def: ActorDef,
+        actor_def: impl IntoIterator<Item = ActorDef>,
         transitions: impl IntoIterator<Item = Transition>,
     ) -> Self {
+        let mut actor_def: Punctuated<ActorDef, Token![;]> = actor_def.into_iter().collect();
+        actor_def.push_punct(Token![;](Span::call_site()));
         let mut transitions: Punctuated<Transition, Token![;]> = transitions.into_iter().collect();
         transitions.push_punct(Token![;](Span::call_site()));
         Self {
             name_def,
             name_def_semi: Token![;](Span::call_site()),
             version_def: None,
-            states_def,
-            states_def_semi: Token![;](Span::call_site()),
+            actor_defs: actor_def,
             transitions,
         }
     }
@@ -41,10 +45,10 @@ impl Protocol {
     pub(crate) fn with_version(
         name_def: NameDef,
         version_def: VersionDef,
-        states_def: ActorDef,
+        actor_def: impl IntoIterator<Item = ActorDef>,
         transitions: impl IntoIterator<Item = Transition>,
     ) -> Self {
-        let mut protocol = Self::new(name_def, states_def, transitions);
+        let mut protocol = Self::new(name_def, actor_def, transitions);
         protocol.version_def = Some((version_def, Token![;](Span::call_site())));
         protocol
     }
@@ -64,15 +68,13 @@ impl Parse for Protocol {
         } else {
             None
         };
-        let states_def = input.parse()?;
-        let states_def_semi = input.parse()?;
+        let actor_defs = Punctuated::parse_list(input, |input| !input.peek(Token![let]))?;
         let transitions = input.parse_terminated(Transition::parse, Token![;])?;
         Ok(Protocol {
             name_def,
             name_def_semi,
             version_def,
-            states_def,
-            states_def_semi,
+            actor_defs,
             transitions,
         })
     }
@@ -82,9 +84,13 @@ impl GetSpan for Protocol {
     fn span(&self) -> Span {
         self.name_def
             .span()
+            .left_join(
+                self.version_def
+                    .as_ref()
+                    .map(|(left, right)| left.span().left_join(right.span())),
+            )
             .left_join(self.name_def_semi.span())
-            .left_join(&self.states_def)
-            .left_join(self.states_def_semi.span())
+            .left_join(punctuated_span(&self.actor_defs))
             .left_join(punctuated_span(&self.transitions))
     }
 }
@@ -408,13 +414,9 @@ impl StatesList {
 impl Parse for StatesList {
     /// states_list : state ( ',' state )* ','? ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let mut states = Punctuated::new();
-        while !(input.peek(Token![>]) || input.peek(Token![;]) || input.cursor().eof()) {
-            states.push_value(input.parse()?);
-            if let Ok(comma) = input.parse::<Token![,]>() {
-                states.push_punct(comma);
-            }
-        }
+        let states = Punctuated::parse_list(input, |input| {
+            input.peek(Token![>]) || input.peek(Token![;])
+        })?;
         if states.is_empty() {
             return Err(input.error(Self::EMPTY_ERR));
         }
@@ -438,6 +440,8 @@ impl AsRef<Punctuated<State, Token![,]>> for StatesList {
 pub(crate) struct DestList(Punctuated<Dest, Token![,]>);
 
 impl DestList {
+    const TRAILING_COMMA_ERR: &str = "No trailing comma is allowed in a destination list.";
+
     fn empty() -> Self {
         Self(Punctuated::new())
     }
@@ -446,12 +450,12 @@ impl DestList {
 impl Parse for DestList {
     /// dest_list : dest ( ',' dest )* ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let mut dests = Punctuated::new();
-        while !(input.peek(Token![;]) || input.cursor().eof()) {
-            dests.push_value(input.parse()?);
-            if let Ok(comma) = input.parse::<Token![,]>() {
-                dests.push_punct(comma);
-            }
+        let dests = Punctuated::parse_list(input, |input| input.peek(Token![;]))?;
+        if dests.trailing_punct() {
+            return Err(syn::Error::new(
+                punctuated_span(&dests),
+                Self::TRAILING_COMMA_ERR,
+            ));
         }
         Ok(DestList(dests))
     }
@@ -741,6 +745,32 @@ pub(crate) fn new_ident(str: &str) -> Ident {
     Ident::new(str, Span::call_site())
 }
 
+trait ParsePunctuatedList: Sized {
+    fn parse_list(
+        input: ParseStream,
+        should_break: impl Fn(ParseStream) -> bool,
+    ) -> syn::Result<Self>;
+}
+
+impl<T: Parse, U: Parse> ParsePunctuatedList for Punctuated<T, U> {
+    fn parse_list(
+        input: ParseStream,
+        should_break: impl Fn(ParseStream) -> bool,
+    ) -> syn::Result<Self> {
+        let mut output = Punctuated::new();
+        while !input.cursor().eof() {
+            if should_break(input) {
+                break;
+            }
+            output.push_value(input.parse()?);
+            if let Ok(punct) = input.parse() {
+                output.push_punct(punct);
+            }
+        }
+        Ok(output)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -764,7 +794,7 @@ let {EXPECTED_ACTOR} = [{}];
         );
         let expected = Protocol::new(
             NameDef::new(EXPECTED_NAME),
-            ActorDef::new(EXPECTED_ACTOR, EXPECTED_STATES),
+            [ActorDef::new(EXPECTED_ACTOR, EXPECTED_STATES)],
             [
                 Transition::new(
                     State::new(EXPECTED_STATES[0], []),
@@ -798,7 +828,7 @@ Init?Activate -> End;"
         let expected = Protocol::with_version(
             NameDef::new("VersionTest"),
             VersionDef::new(EXPECTED_VERSION),
-            ActorDef::new("actor", ["Init"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -824,7 +854,7 @@ Init?Activate -> End;"
         let expected = Protocol::with_version(
             NameDef::new("VersionTest"),
             VersionDef::new(EXPECTED_VERSION + 1),
-            ActorDef::new("actor", ["Init"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -1102,6 +1132,17 @@ Init?Activate -> End;"
         assert_eq!(expected, actual);
     }
 
+    #[test]
+    fn dest_list_trailing_comma_err() {
+        const INPUT: &str = "First!Msg0, Second!Msg1,";
+
+        let result = parse_str::<DestList>(INPUT);
+
+        assert!(result.is_err());
+        let err_str = result.err().unwrap().to_string();
+        assert_eq!(DestList::TRAILING_COMMA_ERR, err_str);
+    }
+
     #[test]
     fn destination_state_parse_regular() {
         const EXPECTED_DEST_STATE: &str = "Listening";

+ 300 - 127
crates/btproto/src/validation.rs

@@ -1,8 +1,8 @@
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 
 use proc_macro2::{Ident, Span};
 
-use btrun::{Activate, End};
+use btrun::End;
 
 use crate::{
     error::MaybeErr,
@@ -12,13 +12,61 @@ use crate::{
 
 impl Protocol {
     pub(crate) fn validate(&self) -> syn::Result<()> {
-        self.all_states_declared_and_used()
-            .combine(self.match_receivers_and_senders())
-            .combine(self.no_undeliverable_msgs())
-            .combine(self.valid_replies())
-            .combine(self.msg_sent_or_received())
+        let validator = ProtocolValidator::new(self);
+        validator
+            .all_states_declared_and_used()
+            .combine(validator.receivers_and_senders_matched())
+            .combine(validator.no_undeliverable_msgs())
+            .combine(validator.replies_expected())
+            .combine(validator.msg_sent_or_received())
+            .combine(validator.clients_only_receive_replies())
             .into()
     }
+}
+
+struct ProtocolValidator<'a> {
+    protocol: &'a Protocol,
+    actors_by_states: HashMap<&'a Ident, &'a Ident>,
+    client_actors: HashSet<&'a Ident>,
+}
+
+impl<'a> ProtocolValidator<'a> {
+    fn new(protocol: &'a Protocol) -> Self {
+        let mut actors_by_states: HashMap<&Ident, &Ident> = HashMap::new();
+        for actor_def in protocol.actor_defs.iter() {
+            for state in actor_def.states.as_ref().iter() {
+                actors_by_states.insert(state, &actor_def.actor);
+            }
+        }
+        let client_actors: HashSet<&Ident> = protocol
+            .transitions
+            .iter()
+            .filter(|transition| transition.in_msg().is_none())
+            .map(|transition| actors_by_states.get(&transition.in_state.state_trait))
+            .filter(|option| option.is_some())
+            .map(|option| *option.unwrap())
+            .collect();
+        Self {
+            protocol,
+            actors_by_states,
+            client_actors,
+        }
+    }
+
+    /// Returns the [Ident] for the actor that the given state is a part of.
+    fn actor(&self, state: &Ident) -> Option<&Ident> {
+        self.actors_by_states.get(state).copied()
+    }
+
+    fn is_client(&self, actor: &Ident) -> bool {
+        self.client_actors.contains(actor)
+    }
+
+    fn is_client_state(&self, state: &State) -> bool {
+        self.actor(&state.state_trait)
+            .map(|actor| self.is_client(actor))
+            .unwrap_or(false)
+    }
 
     const UNDECLARED_STATE_ERR: &str = "State was not declared.";
     const UNUSED_STATE_ERR: &str = "State was declared but never used.";
@@ -26,15 +74,15 @@ impl Protocol {
     /// Verifies that every state which is used has been declared, except for the End state.
     fn all_states_declared_and_used(&self) -> MaybeErr {
         let end = Ident::new(End::ident(), Span::call_site());
-        let declared: HashSet<&Ident> = self
-            .states_def
-            .states
-            .as_ref()
-            .iter()
-            .chain([&end].into_iter())
-            .collect();
+        let mut declared: HashSet<&Ident> = HashSet::new();
+        declared.insert(&end);
+        for actor_def in self.protocol.actor_defs.iter() {
+            for state in actor_def.states.as_ref().iter() {
+                declared.insert(state);
+            }
+        }
         let mut used: HashSet<&Ident> = HashSet::with_capacity(declared.len());
-        for transition in self.transitions.iter() {
+        for transition in self.protocol.transitions.iter() {
             let in_state = &transition.in_state;
             used.insert(&in_state.state_trait);
             used.extend(in_state.owned_states.as_ref().iter());
@@ -46,7 +94,7 @@ impl Protocol {
                 used.extend(out_states.owned_states.as_ref().iter());
             }
             // We don't have to check the states referred to in out_msgs because the
-            // match_receivers_and_senders method ensures that each of these exists in a receiver
+            // receivers_and_senders_matched method ensures that each of these exists in a receiver
             // position.
         }
         let undeclared: MaybeErr = used
@@ -65,19 +113,13 @@ impl Protocol {
     const UNMATCHED_RECEIVER_ERR: &str = "No sender found for message type.";
 
     /// Ensures that the recipient state for every sent message has a receiving transition
-    /// defined, and every receiver has a sender (except for the Activate message which is sent
-    /// by the runtime).
-    fn match_receivers_and_senders(&self) -> MaybeErr {
+    /// defined, and every receiver has a sender.
+    fn receivers_and_senders_matched(&self) -> MaybeErr {
         let mut senders: HashSet<(&State, &Message)> = HashSet::new();
         let mut receivers: HashSet<(&State, &Message)> = HashSet::new();
-        for transition in self.transitions.iter() {
+        for transition in self.protocol.transitions.iter() {
             if let Some(msg) = transition.in_msg() {
                 receivers.insert((&transition.in_state, msg));
-                if msg.msg_type == Activate::ident() {
-                    // The Activate message is sent by the run time, so a sender is created to
-                    // represent it.
-                    senders.insert((&transition.in_state, msg));
-                }
             }
             for dest in transition.out_msgs.as_ref().iter() {
                 let dest_state = match &dest.state {
@@ -106,7 +148,7 @@ impl Protocol {
     /// reply.
     fn no_undeliverable_msgs(&self) -> MaybeErr {
         let mut err = MaybeErr::none();
-        for transition in self.transitions.iter() {
+        for transition in self.protocol.transitions.iter() {
             let mut allowed_states: Option<HashSet<&Ident>> = None;
             for dest in transition.out_msgs.as_ref().iter() {
                 if dest.msg.is_reply() {
@@ -145,10 +187,10 @@ impl Protocol {
     const MULTIPLE_REPLIES_ERR: &str =
         "Only a single reply can be sent in response to any message.";
 
-    /// Verifies that replies are only sent in response to messages.
-    fn valid_replies(&self) -> MaybeErr {
+    /// Verifies that exactly one reply is sent in response to a previously sent message.
+    fn replies_expected(&self) -> MaybeErr {
         let mut err = MaybeErr::none();
-        for transition in self.transitions.iter() {
+        for transition in self.protocol.transitions.iter() {
             let replies: Vec<_> = transition
                 .out_msgs
                 .as_ref()
@@ -189,7 +231,8 @@ impl Protocol {
     /// this is that if no message is sent or received, then the state transition is unobserved by
     /// other actors, and so should not be in a protocol.
     fn msg_sent_or_received(&self) -> MaybeErr {
-        self.transitions
+        self.protocol
+            .transitions
             .iter()
             .filter(|transition| {
                 transition.in_msg().is_none() && transition.out_msgs.as_ref().is_empty()
@@ -197,6 +240,29 @@ impl Protocol {
             .map(|transition| syn::Error::new(transition.span(), Self::NO_MSG_SENT_OR_RECEIVED_ERR))
             .collect()
     }
+
+    const CLIENT_RECEIVED_NON_REPLY_ERR: &str =
+        "A client actor cannot receive a message which is not a reply.";
+
+    /// A client is any actor with a state that sends at least one message when not handling an
+    /// incoming message. Such actors are not allowed to receive any message which are not replies.
+    fn clients_only_receive_replies(&self) -> MaybeErr {
+        self.protocol
+            .transitions
+            .iter()
+            .filter(|transition| {
+                if let Some(msg) = transition.in_msg() {
+                    if !msg.is_reply() {
+                        return self.is_client_state(&transition.in_state);
+                    }
+                }
+                false
+            })
+            .map(|transition| {
+                syn::Error::new(transition.span(), Self::CLIENT_RECEIVED_NON_REPLY_ERR)
+            })
+            .collect()
+    }
 }
 
 #[cfg(test)]
@@ -224,19 +290,34 @@ mod tests {
         const STATE_NAME: &str = "Init";
         Protocol::new(
             NameDef::new("Test"),
-            ActorDef::new("actor", [STATE_NAME]),
-            [Transition::new(
-                State::new(STATE_NAME, []),
-                Some(Message::new("Activate", false, [])),
-                [State::new("End", [])],
-                [],
-            )],
+            [
+                ActorDef::new("server", [STATE_NAME]),
+                ActorDef::new("client", ["Client"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("End", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new(STATE_NAME, [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new(STATE_NAME, []),
+                    Some(Message::new("Msg", false, [])),
+                    [State::new("End", [])],
+                    [],
+                ),
+            ],
         )
     }
 
     #[test]
     fn all_states_declared_and_used_ok() {
-        let result = min_protocol().all_states_declared_and_used();
+        let protocol = min_protocol();
+        let result = ProtocolValidator::new(&protocol).all_states_declared_and_used();
 
         assert_ok!(result);
     }
@@ -244,9 +325,9 @@ mod tests {
     #[test]
     fn all_states_declared_and_used_end_not_used_ok() {
         const STATE_NAME: &str = "Init";
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Test"),
-            ActorDef::new("actor", [STATE_NAME]),
+            [ActorDef::new("actor", [STATE_NAME])],
             [Transition::new(
                 State::new(STATE_NAME, []),
                 Some(Message::new("Activate", false, [])),
@@ -254,6 +335,7 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.all_states_declared_and_used();
 
@@ -262,9 +344,9 @@ mod tests {
 
     #[test]
     fn all_states_declared_and_used_undeclared_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeclared"),
-            ActorDef::new("actor", ["Init"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -272,17 +354,18 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.all_states_declared_and_used();
 
-        assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
+        assert_err!(result, ProtocolValidator::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_undeclared_out_state_owned_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeclared"),
-            ActorDef::new("actor", ["Init", "Next"]),
+            [ActorDef::new("actor", ["Init", "Next"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -290,17 +373,18 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.all_states_declared_and_used();
 
-        assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
+        assert_err!(result, ProtocolValidator::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_undeclared_in_state_owned_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeclared"),
-            ActorDef::new("actor", ["Init", "Next"]),
+            [ActorDef::new("actor", ["Init", "Next"])],
             [Transition::new(
                 State::new("Init", ["Undeclared"]),
                 Some(Message::new("Activate", false, [])),
@@ -308,17 +392,18 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.all_states_declared_and_used();
 
-        assert_err!(result, Protocol::UNDECLARED_STATE_ERR);
+        assert_err!(result, ProtocolValidator::UNDECLARED_STATE_ERR);
     }
 
     #[test]
     fn all_states_declared_and_used_unused_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Unused"),
-            ActorDef::new("actor", ["Init", "Extra"]),
+            [ActorDef::new("actor", ["Init", "Extra"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -326,74 +411,48 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.all_states_declared_and_used();
 
-        assert_err!(result, Protocol::UNUSED_STATE_ERR);
+        assert_err!(result, ProtocolValidator::UNUSED_STATE_ERR);
     }
 
     #[test]
-    fn match_receivers_and_senders_ok() {
-        let result = min_protocol().match_receivers_and_senders();
+    fn receivers_and_senders_matched_ok() {
+        let protocol = min_protocol();
+        let result = ProtocolValidator::new(&protocol).receivers_and_senders_matched();
 
         assert_ok!(result);
     }
 
     #[test]
-    fn match_receivers_and_senders_send_activate_ok() {
-        let input = Protocol::new(
+    fn receivers_and_senders_matched_unmatched_sender_err() {
+        let protocol = Protocol::new(
             NameDef::new("Unbalanced"),
-            ActorDef::new("actor", ["First", "Second"]),
-            [
-                Transition::new(
-                    State::new("First", []),
-                    Some(Message::new("Activate", false, [])),
-                    [State::new("First", [])],
-                    [Dest::new(
-                        DestinationState::Individual(State::new("Second", [])),
-                        Message::new("Activate", false, []),
-                    )],
-                ),
-                Transition::new(
-                    State::new("Second", []),
-                    Some(Message::new("Activate", false, [])),
-                    [State::new("Second", [])],
-                    [],
-                ),
-            ],
-        );
-
-        let result = input.match_receivers_and_senders();
-
-        assert_ok!(result);
-    }
-
-    #[test]
-    fn match_receivers_and_senders_unmatched_sender_err() {
-        let input = Protocol::new(
-            NameDef::new("Unbalanced"),
-            ActorDef::new("actor", ["Init", "Other"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
-                Some(Message::new("Activate", false, [])),
+                None,
                 [State::new("Init", [])],
                 [Dest::new(
-                    DestinationState::Individual(State::new("Other", [])),
-                    Message::new("Activate", false, []),
+                    DestinationState::Service(State::new("Init", [])),
+                    Message::new("Msg", false, []),
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
-        let result = input.match_receivers_and_senders();
+        let result = input.receivers_and_senders_matched();
 
-        assert_err!(result, Protocol::UNMATCHED_SENDER_ERR);
+        assert_err!(result, ProtocolValidator::UNMATCHED_SENDER_ERR);
     }
 
     #[test]
-    fn match_receivers_and_senders_unmatched_receiver_err() {
-        let input = Protocol::new(
+    fn receivers_and_senders_matched_unmatched_receiver_err() {
+        let protocol = Protocol::new(
             NameDef::new("Unbalanced"),
-            ActorDef::new("actor", ["Init"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("NotExists", false, [])),
@@ -401,24 +460,26 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
-        let result = input.match_receivers_and_senders();
+        let result = input.receivers_and_senders_matched();
 
-        assert_err!(result, Protocol::UNMATCHED_RECEIVER_ERR);
+        assert_err!(result, ProtocolValidator::UNMATCHED_RECEIVER_ERR);
     }
 
     #[test]
     fn no_undeliverable_msgs_ok() {
-        let result = min_protocol().no_undeliverable_msgs();
+        let protocol = min_protocol();
+        let result = ProtocolValidator::new(&protocol).no_undeliverable_msgs();
 
         assert_ok!(result);
     }
 
     #[test]
     fn no_undeliverable_msgs_reply_ok() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeliverable"),
-            ActorDef::new("actor", ["Listening", "Client"]),
+            [ActorDef::new("actor", ["Listening", "Client"])],
             [Transition::new(
                 State::new("Listening", []),
                 Some(Message::new("Msg", false, [])),
@@ -429,6 +490,7 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.no_undeliverable_msgs();
 
@@ -437,9 +499,9 @@ mod tests {
 
     #[test]
     fn no_undeliverable_msgs_service_ok() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeliverable"),
-            ActorDef::new("actor", ["Client", "Server"]),
+            [ActorDef::new("actor", ["Client", "Server"])],
             [Transition::new(
                 State::new("Client", []),
                 None,
@@ -450,6 +512,7 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.no_undeliverable_msgs();
 
@@ -458,9 +521,9 @@ mod tests {
 
     #[test]
     fn no_undeliverable_msgs_owned_ok() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeliverable"),
-            ActorDef::new("actor", ["FileClient", "FileHandle"]),
+            [ActorDef::new("actor", ["FileClient", "FileHandle"])],
             [Transition::new(
                 State::new("FileClient", ["FileHandle"]),
                 None,
@@ -471,6 +534,7 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.no_undeliverable_msgs();
 
@@ -479,9 +543,9 @@ mod tests {
 
     #[test]
     fn no_undeliverable_msgs_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Undeliverable"),
-            ActorDef::new("actor", ["Client", "Server"]),
+            [ActorDef::new("actor", ["Client", "Server"])],
             [Transition::new(
                 State::new("Client", []),
                 None,
@@ -492,17 +556,18 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.no_undeliverable_msgs();
 
-        assert_err!(result, Protocol::UNDELIVERABLE_ERR);
+        assert_err!(result, ProtocolValidator::UNDELIVERABLE_ERR);
     }
 
     #[test]
-    fn valid_replies_ok() {
-        let input = Protocol::new(
+    fn replies_expected_ok() {
+        let protocol = Protocol::new(
             NameDef::new("ValidReplies"),
-            ActorDef::new("actor", ["Client", "Server"]),
+            [ActorDef::new("actor", ["Client", "Server"])],
             [Transition::new(
                 State::new("Server", []),
                 Some(Message::new("Msg", false, [])),
@@ -513,17 +578,18 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
-        let result = input.valid_replies();
+        let result = input.replies_expected();
 
         assert_ok!(result);
     }
 
     #[test]
-    fn valid_replies_invalid_reply_err() {
-        let input = Protocol::new(
+    fn replies_expected_invalid_reply_err() {
+        let protocol = Protocol::new(
             NameDef::new("ValidReplies"),
-            ActorDef::new("actor", ["Client", "Server"]),
+            [ActorDef::new("actor", ["Client", "Server"])],
             [Transition::new(
                 State::new("Client", []),
                 None,
@@ -534,17 +600,18 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
-        let result = input.valid_replies();
+        let result = input.replies_expected();
 
-        assert_err!(result, Protocol::INVALID_REPLY_ERR);
+        assert_err!(result, ProtocolValidator::INVALID_REPLY_ERR);
     }
 
     #[test]
-    fn valid_replies_multiple_replies_err() {
-        let input = Protocol::new(
+    fn replies_expected_multiple_replies_err() {
+        let protocol = Protocol::new(
             NameDef::new("ValidReplies"),
-            ActorDef::new("actor", ["Client", "OtherClient", "Server"]),
+            [ActorDef::new("actor", ["Client", "OtherClient", "Server"])],
             [Transition::new(
                 State::new("Server", []),
                 Some(Message::new("Msg", false, [])),
@@ -561,17 +628,18 @@ mod tests {
                 ],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
-        let result = input.valid_replies();
+        let result = input.replies_expected();
 
-        assert_err!(result, Protocol::MULTIPLE_REPLIES_ERR);
+        assert_err!(result, ProtocolValidator::MULTIPLE_REPLIES_ERR);
     }
 
     #[test]
     fn msg_sent_or_received_msg_received_ok() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Test"),
-            ActorDef::new("actor", ["Init"]),
+            [ActorDef::new("actor", ["Init"])],
             [Transition::new(
                 State::new("Init", []),
                 Some(Message::new("Activate", false, [])),
@@ -579,6 +647,7 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.msg_sent_or_received();
 
@@ -587,9 +656,9 @@ mod tests {
 
     #[test]
     fn msg_sent_or_received_msg_sent_ok() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Test"),
-            ActorDef::new("actor", ["First", "Second"]),
+            [ActorDef::new("actor", ["First", "Second"])],
             [Transition::new(
                 State::new("First", []),
                 None,
@@ -600,6 +669,7 @@ mod tests {
                 )],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.msg_sent_or_received();
 
@@ -608,9 +678,9 @@ mod tests {
 
     #[test]
     fn msg_sent_or_received_neither_err() {
-        let input = Protocol::new(
+        let protocol = Protocol::new(
             NameDef::new("Test"),
-            ActorDef::new("actor", ["First"]),
+            [ActorDef::new("actor", ["First"])],
             [Transition::new(
                 State::new("First", []),
                 None,
@@ -618,9 +688,112 @@ mod tests {
                 [],
             )],
         );
+        let input = ProtocolValidator::new(&protocol);
 
         let result = input.msg_sent_or_received();
 
-        assert_err!(result, Protocol::NO_MSG_SENT_OR_RECEIVED_ERR);
+        assert_err!(result, ProtocolValidator::NO_MSG_SENT_OR_RECEIVED_ERR);
+    }
+
+    #[test]
+    fn clients_only_receive_replies_ok() {
+        let protocol = Protocol::new(
+            NameDef::new("ClientReplies"),
+            [
+                ActorDef::new("client", ["Client", "Waiting"]),
+                ActorDef::new("server", ["Init", "Listening"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Init", []),
+                    Some(Message::new("Activate", false, [])),
+                    [State::new("Listening", [])],
+                    [],
+                ),
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Waiting", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Listening", [])),
+                        Message::new("Ping", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Listening", []),
+                    Some(Message::new("Ping", false, [])),
+                    [State::new("Listening", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Waiting", [])),
+                        Message::new("Ping", true, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Waiting", []),
+                    Some(Message::new("Ping", true, [])),
+                    [State::new("Client", [])],
+                    [],
+                ),
+            ],
+        );
+        let input = ProtocolValidator::new(&protocol);
+
+        let result = input.clients_only_receive_replies();
+
+        assert_ok!(result);
+    }
+
+    #[test]
+    fn clients_only_receive_replies_err() {
+        let protocol = Protocol::new(
+            NameDef::new("ClientReplies"),
+            [
+                ActorDef::new("client", ["Client", "Waiting"]),
+                ActorDef::new("server", ["Init", "Listening"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Init", []),
+                    Some(Message::new("Activate", false, [])),
+                    [State::new("Listening", [])],
+                    [],
+                ),
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Waiting", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Listening", [])),
+                        Message::new("Ping", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Listening", []),
+                    Some(Message::new("Ping", false, [])),
+                    [State::new("Listening", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Waiting", [])),
+                        Message::new("Ping", true, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Waiting", []),
+                    Some(Message::new("Ping", true, [])),
+                    [State::new("Client", [])],
+                    [],
+                ),
+                Transition::new(
+                    State::new("Client", []),
+                    Some(Message::new("Activate", false, [])),
+                    [State::new("Client", [])],
+                    [],
+                ),
+            ],
+        );
+        let input = ProtocolValidator::new(&protocol);
+
+        let result = input.clients_only_receive_replies();
+
+        assert_err!(result, ProtocolValidator::CLIENT_RECEIVED_NON_REPLY_ERR);
     }
 }

+ 14 - 24
crates/btproto/tests/protocol_tests.rs

@@ -2,7 +2,7 @@ use std::future::{ready, Ready};
 
 use btlib::Result;
 use btproto::protocol;
-use btrun::{Activate, CallMsg, End};
+use btrun::{CallMsg, End};
 use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize)]
@@ -21,23 +21,27 @@ macro_rules! assert_type {
 
 #[test]
 fn minimal_syntax() {
+    pub struct Msg;
+
     protocol! {
         named MinimalTest;
-        let states = [Init];
-        Init?Activate -> End;
+        let states = [Server];
+        let client = [Client];
+        Client -> End, >service(Server)!Msg;
+        Server?Msg -> End;
     }
 
     let msg: Option<MinimalTestMsgs> = None;
     match msg {
-        Some(MinimalTestMsgs::Activate(act)) => assert_type!(act, Activate),
+        Some(MinimalTestMsgs::Msg(act)) => assert_type!(act, Msg),
         None => (),
     }
 
-    struct InitState;
+    struct ServerState;
 
-    impl Init for InitState {
-        type HandleActivateFut = Ready<Result<End>>;
-        fn handle_activate(self, _msg: btrun::Activate) -> Self::HandleActivateFut {
+    impl Server for ServerState {
+        type HandleMsgFut = Ready<Result<End>>;
+        fn handle_msg(self, _msg: Msg) -> Self::HandleMsgFut {
             ready(Ok(End))
         }
     }
@@ -47,11 +51,8 @@ fn minimal_syntax() {
 fn reply() {
     protocol! {
         named ReplyTest;
-        let states = [
-            ServerInit, Listening,
-            Client, Waiting,
-        ];
-        ServerInit?Activate -> Listening;
+        let server = [Listening];
+        let client = [Client, Waiting];
         Client -> Waiting, >service(Listening)!Ping;
         Listening?Ping -> Listening, >Waiting!Ping::Reply;
         Waiting?Ping::Reply -> End;
@@ -59,22 +60,11 @@ fn reply() {
 
     let msg: Option<ReplyTestMsgs> = None;
     match msg {
-        Some(ReplyTestMsgs::Activate(act)) => assert_type!(act, Activate),
         Some(ReplyTestMsgs::Ping(ping)) => assert_type!(ping, Ping),
         Some(ReplyTestMsgs::PingReply(reply)) => assert_type!(reply, <Ping as CallMsg>::Reply),
         None => (),
     }
 
-    struct ServerInitState;
-
-    impl ServerInit for ServerInitState {
-        type HandleActivateListening = ListeningState;
-        type HandleActivateFut = Ready<Result<ListeningState>>;
-        fn handle_activate(self, _msg: btrun::Activate) -> Self::HandleActivateFut {
-            ready(Ok(ListeningState))
-        }
-    }
-
     struct ListeningState;
 
     impl Listening for ListeningState {

+ 0 - 5
crates/btrun/src/lib.rs

@@ -309,11 +309,6 @@ impl Activate {
     pub fn new(rt: &'static Runtime, act_id: Uuid) -> Self {
         Self { rt, act_id }
     }
-
-    /// Returns the identifier expected for this message in protocol definitions.
-    pub fn ident() -> &'static str {
-        stringify!(Activate)
-    }
 }
 
 /// Deserializes replies sent over the wire.

+ 10 - 14
crates/btrun/tests/runtime_tests.rs

@@ -154,14 +154,11 @@ mod ping_pong {
     // simple ping-pong protocol:
     protocol! {
         named PingPongProtocol;
-        let states = [
-            ClientInit, SentPing,
-            ServerInit, Listening,
-        ];
-        ClientInit?Activate -> SentPing, >service(Listening)!Ping;
-        ServerInit?Activate -> Listening;
-        Listening?Ping -> End, >SentPing!Ping::Reply;
-        SentPing?Ping::Reply -> End;
+        let server = [Server];
+        let client = [Client, Waiting];
+        Client -> Waiting, >service(Server)!Ping;
+        Server?Ping -> End, >Waiting!Ping::Reply;
+        Waiting?Ping::Reply -> End;
     }
     //
     // In words, the protocol is described as follows.
@@ -378,12 +375,11 @@ mod travel_agency {
     // (`?`).
     protocol! {
         named TravelAgency;
-        let states = [
-            AgencyInit, Listening,
-            Choosing,
-        ];
-        AgencyInit?Activate -> Listening;
-        Choosing -> Choosing, >service(Listening)!Query, service(Listening)!Accept, service(Listening)!Reject;
+        let agency = [Listening];
+        let customer = [Choosing];
+        Choosing -> Choosing, >service(Listening)!Query;
+        Choosing -> Choosing, >service(Listening)!Accept;
+        Choosing -> Choosing, >service(Listening)!Reject;
         Listening?Query -> Listening, >Choosing!Query::Reply;
         Choosing?Query::Reply -> Choosing;
         Listening?Accept -> End, >Choosing!Accept::Reply;

+ 2 - 2
crates/btsector/src/lib.rs

@@ -59,8 +59,8 @@ pub enum SectorMsgReply {
 
 protocol! {
     named SectorProtocol;
-    let states = [ServerInit, Listening, Client];
-    ServerInit?Activate -> Listening;
+    let server = [Listening];
+    let client = [Client];
     Client -> Client, >service(Listening)!SectorMsg;
     Listening?SectorMsg -> Listening, >Client!SectorMsg::Reply;
     Client?SectorMsg::Reply -> Client;