|
@@ -1,4 +1,4 @@
|
|
-use std::collections::HashSet;
|
|
|
|
|
|
+use std::{collections::HashSet, hash::Hash};
|
|
|
|
|
|
use proc_macro2::{Ident, Span};
|
|
use proc_macro2::{Ident, Span};
|
|
|
|
|
|
@@ -6,8 +6,8 @@ use btrun::End;
|
|
|
|
|
|
use crate::{
|
|
use crate::{
|
|
error::{self, MaybeErr},
|
|
error::{self, MaybeErr},
|
|
- model::ProtocolModel,
|
|
|
|
- parsing::{DestinationState, GetSpan, Message, State},
|
|
|
|
|
|
+ model::{MsgInfo, ProtocolModel},
|
|
|
|
+ parsing::{DestinationState, GetSpan, State},
|
|
};
|
|
};
|
|
|
|
|
|
impl ProtocolModel {
|
|
impl ProtocolModel {
|
|
@@ -18,6 +18,7 @@ impl ProtocolModel {
|
|
.combine(self.no_undeliverable_msgs())
|
|
.combine(self.no_undeliverable_msgs())
|
|
.combine(self.replies_expected())
|
|
.combine(self.replies_expected())
|
|
.combine(self.clients_only_receive_replies())
|
|
.combine(self.clients_only_receive_replies())
|
|
|
|
+ .combine(self.no_unobservable_states())
|
|
.into()
|
|
.into()
|
|
}
|
|
}
|
|
|
|
|
|
@@ -67,12 +68,12 @@ impl ProtocolModel {
|
|
}
|
|
}
|
|
let undeclared: MaybeErr = used
|
|
let undeclared: MaybeErr = used
|
|
.difference(&declared)
|
|
.difference(&declared)
|
|
- .map(|ident| syn::Error::new(ident.span(), error::msgs::UNDECLARED_STATE_ERR))
|
|
|
|
|
|
+ .map(|ident| syn::Error::new(ident.span(), error::msgs::UNDECLARED_STATE))
|
|
.collect();
|
|
.collect();
|
|
let unused: MaybeErr = declared
|
|
let unused: MaybeErr = declared
|
|
.difference(&used)
|
|
.difference(&used)
|
|
.filter(|ident| **ident != End::ident())
|
|
.filter(|ident| **ident != End::ident())
|
|
- .map(|ident| syn::Error::new(ident.span(), error::msgs::UNUSED_STATE_ERR))
|
|
|
|
|
|
+ .map(|ident| syn::Error::new(ident.span(), error::msgs::UNUSED_STATE))
|
|
.collect();
|
|
.collect();
|
|
undeclared.combine(unused)
|
|
undeclared.combine(unused)
|
|
}
|
|
}
|
|
@@ -80,29 +81,94 @@ impl ProtocolModel {
|
|
/// Ensures that the recipient state for every sent message has a receiving transition
|
|
/// Ensures that the recipient state for every sent message has a receiving transition
|
|
/// defined, and every receiver has a sender. Note that each message isn't required to have a
|
|
/// defined, and every receiver has a sender. Note that each message isn't required to have a
|
|
/// unique sender or a unique receiver, just that at least one of each much be defined.
|
|
/// unique sender or a unique receiver, just that at least one of each much be defined.
|
|
- 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.def().transitions.iter() {
|
|
|
|
- if let Some(msg) = transition.in_msg() {
|
|
|
|
- receivers.insert((&transition.in_state, msg));
|
|
|
|
|
|
+ fn receivers_and_senders_matched<'s>(&'s self) -> MaybeErr {
|
|
|
|
+ #[cfg_attr(test, derive(Debug))]
|
|
|
|
+ struct MsgEndpoint<'a> {
|
|
|
|
+ state: &'a State,
|
|
|
|
+ msg_info: &'a MsgInfo,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl<'a> MsgEndpoint<'a> {
|
|
|
|
+ fn new(state: &'a State, msg_info: &'a MsgInfo) -> Self {
|
|
|
|
+ Self { state, msg_info }
|
|
}
|
|
}
|
|
- for dest in transition.out_msgs.as_ref().iter() {
|
|
|
|
- let dest_state = match &dest.state {
|
|
|
|
- DestinationState::Individual(dest_state) => dest_state,
|
|
|
|
- DestinationState::Service(dest_state) => dest_state,
|
|
|
|
- };
|
|
|
|
- senders.insert((dest_state, &dest.msg));
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl<'a> PartialEq for MsgEndpoint<'a> {
|
|
|
|
+ fn eq(&self, other: &Self) -> bool {
|
|
|
|
+ self.state.state_trait == other.state.state_trait
|
|
|
|
+ && self.msg_info.def().msg_type == self.msg_info.def().msg_type
|
|
|
|
+ && self.msg_info.is_reply() == self.msg_info.is_reply()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl<'a> Eq for MsgEndpoint<'a> {}
|
|
|
|
+
|
|
|
|
+ impl<'a> Hash for MsgEndpoint<'a> {
|
|
|
|
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
+ self.state.state_trait.hash(state);
|
|
|
|
+ self.msg_info.def().msg_type.hash(state);
|
|
|
|
+ self.msg_info.is_reply().hash(state);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[cfg(test)]
|
|
|
|
+ impl<'a> std::fmt::Display for MsgEndpoint<'a> {
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
+ write!(
|
|
|
|
+ f,
|
|
|
|
+ "({}, {})",
|
|
|
|
+ self.state.state_trait,
|
|
|
|
+ self.msg_info.msg_name()
|
|
|
|
+ )
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- let extra_senders: MaybeErr = senders
|
|
|
|
- .difference(&receivers)
|
|
|
|
- .map(|pair| syn::Error::new(pair.1.msg_type.span(), error::msgs::UNMATCHED_SENDER_ERR))
|
|
|
|
|
|
+
|
|
|
|
+ let msgs = self.msg_lookup();
|
|
|
|
+ let mut outgoing: HashSet<MsgEndpoint<'s>> = HashSet::new();
|
|
|
|
+ let mut incoming: HashSet<MsgEndpoint<'s>> = HashSet::new();
|
|
|
|
+ for actor in self.actors_iter() {
|
|
|
|
+ for method in actor
|
|
|
|
+ .states()
|
|
|
|
+ .values()
|
|
|
|
+ .flat_map(|state| state.methods().values())
|
|
|
|
+ {
|
|
|
|
+ let transition = method.def();
|
|
|
|
+ if let Some(msg) = transition.in_msg() {
|
|
|
|
+ let msg_info = msgs.lookup(msg);
|
|
|
|
+ incoming.insert(MsgEndpoint::new(&transition.in_state, msg_info));
|
|
|
|
+ }
|
|
|
|
+ for dest in transition.out_msgs.as_ref().iter() {
|
|
|
|
+ let dest_state = match &dest.state {
|
|
|
|
+ DestinationState::Individual(dest_state) => dest_state,
|
|
|
|
+ DestinationState::Service(dest_state) => dest_state,
|
|
|
|
+ };
|
|
|
|
+ let msg_info = self.msg_lookup().lookup(&dest.msg);
|
|
|
|
+ outgoing.insert(MsgEndpoint::new(dest_state, msg_info));
|
|
|
|
+ if actor.is_client() {
|
|
|
|
+ if let Some(reply) = msg_info.reply() {
|
|
|
|
+ incoming.insert(MsgEndpoint::new(&transition.in_state, reply));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ let extra_senders: MaybeErr = outgoing
|
|
|
|
+ .difference(&incoming)
|
|
|
|
+ .map(|endpoint| {
|
|
|
|
+ syn::Error::new(
|
|
|
|
+ endpoint.msg_info.def().span(),
|
|
|
|
+ error::msgs::UNMATCHED_OUTGOING,
|
|
|
|
+ )
|
|
|
|
+ })
|
|
.collect();
|
|
.collect();
|
|
- let extra_receivers: MaybeErr = receivers
|
|
|
|
- .difference(&senders)
|
|
|
|
- .map(|pair| {
|
|
|
|
- syn::Error::new(pair.1.msg_type.span(), error::msgs::UNMATCHED_RECEIVER_ERR)
|
|
|
|
|
|
+ let extra_receivers: MaybeErr = incoming
|
|
|
|
+ .difference(&outgoing)
|
|
|
|
+ .map(|endpoint| {
|
|
|
|
+ syn::Error::new(
|
|
|
|
+ endpoint.msg_info.def().span(),
|
|
|
|
+ error::msgs::UNMATCHED_INCOMING,
|
|
|
|
+ )
|
|
})
|
|
})
|
|
.collect();
|
|
.collect();
|
|
extra_senders.combine(extra_receivers)
|
|
extra_senders.combine(extra_receivers)
|
|
@@ -142,7 +208,7 @@ impl ProtocolModel {
|
|
err = err.combine(
|
|
err = err.combine(
|
|
syn::Error::new(
|
|
syn::Error::new(
|
|
dest_state.state_trait.span(),
|
|
dest_state.state_trait.span(),
|
|
- error::msgs::UNDELIVERABLE_ERR,
|
|
|
|
|
|
+ error::msgs::UNDELIVERABLE,
|
|
)
|
|
)
|
|
.into(),
|
|
.into(),
|
|
);
|
|
);
|
|
@@ -173,10 +239,7 @@ impl ProtocolModel {
|
|
replies
|
|
replies
|
|
.iter()
|
|
.iter()
|
|
.map(|reply| {
|
|
.map(|reply| {
|
|
- syn::Error::new(
|
|
|
|
- reply.msg_type.span(),
|
|
|
|
- error::msgs::MULTIPLE_REPLIES_ERR,
|
|
|
|
- )
|
|
|
|
|
|
+ syn::Error::new(reply.msg_type.span(), error::msgs::MULTIPLE_REPLIES)
|
|
})
|
|
})
|
|
.collect(),
|
|
.collect(),
|
|
);
|
|
);
|
|
@@ -186,7 +249,7 @@ impl ProtocolModel {
|
|
replies
|
|
replies
|
|
.iter()
|
|
.iter()
|
|
.map(|reply| {
|
|
.map(|reply| {
|
|
- syn::Error::new(reply.msg_type.span(), error::msgs::INVALID_REPLY_ERR)
|
|
|
|
|
|
+ syn::Error::new(reply.msg_type.span(), error::msgs::INVALID_REPLY)
|
|
})
|
|
})
|
|
.collect(),
|
|
.collect(),
|
|
);
|
|
);
|
|
@@ -198,8 +261,7 @@ impl ProtocolModel {
|
|
/// A client is any actor with a state that sends at least one message when not handling an
|
|
/// 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 messages which are not replies.
|
|
/// incoming message. Such actors are not allowed to receive any messages which are not replies.
|
|
fn clients_only_receive_replies(&self) -> MaybeErr {
|
|
fn clients_only_receive_replies(&self) -> MaybeErr {
|
|
- self.actors()
|
|
|
|
- .values()
|
|
|
|
|
|
+ self.actors_iter()
|
|
.filter(|actor| actor.is_client())
|
|
.filter(|actor| actor.is_client())
|
|
.flat_map(|actor| {
|
|
.flat_map(|actor| {
|
|
actor
|
|
actor
|
|
@@ -215,11 +277,27 @@ impl ProtocolModel {
|
|
}
|
|
}
|
|
})
|
|
})
|
|
.map(|transition| {
|
|
.map(|transition| {
|
|
- syn::Error::new(
|
|
|
|
- transition.span(),
|
|
|
|
- error::msgs::CLIENT_RECEIVED_NON_REPLY_ERR,
|
|
|
|
- )
|
|
|
|
|
|
+ syn::Error::new(transition.span(), error::msgs::CLIENT_RECEIVED_NON_REPLY)
|
|
|
|
+ })
|
|
|
|
+ .collect()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Checks that there are no client states which are only receiving replies. Such states can't
|
|
|
|
+ /// be observed, because the methods which sent the original messages will return their replies.
|
|
|
|
+ fn no_unobservable_states(&self) -> MaybeErr {
|
|
|
|
+ self.actors_iter()
|
|
|
|
+ .filter(|actor| actor.is_client())
|
|
|
|
+ .flat_map(|actor| actor.states().values())
|
|
|
|
+ .filter(|state| {
|
|
|
|
+ state.methods().values().all(|method| {
|
|
|
|
+ if let Some(in_msg) = method.def().in_msg() {
|
|
|
|
+ in_msg.is_reply()
|
|
|
|
+ } else {
|
|
|
|
+ false
|
|
|
|
+ }
|
|
|
|
+ })
|
|
})
|
|
})
|
|
|
|
+ .map(|state| syn::Error::new(state.span(), error::msgs::UNOBSERVABLE_STATE))
|
|
.collect()
|
|
.collect()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -229,7 +307,7 @@ mod tests {
|
|
use super::*;
|
|
use super::*;
|
|
use crate::{
|
|
use crate::{
|
|
error::{assert_err, assert_ok},
|
|
error::{assert_err, assert_ok},
|
|
- parsing::{ActorDef, Dest, NameDef, Protocol, Transition},
|
|
|
|
|
|
+ parsing::{ActorDef, Dest, Message, NameDef, Protocol, Transition},
|
|
};
|
|
};
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -277,7 +355,7 @@ mod tests {
|
|
|
|
|
|
let result = input.all_states_declared_and_used();
|
|
let result = input.all_states_declared_and_used();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNDECLARED_STATE_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNDECLARED_STATE);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -296,7 +374,7 @@ mod tests {
|
|
|
|
|
|
let result = input.all_states_declared_and_used();
|
|
let result = input.all_states_declared_and_used();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNDECLARED_STATE_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNDECLARED_STATE);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -315,7 +393,7 @@ mod tests {
|
|
|
|
|
|
let result = input.all_states_declared_and_used();
|
|
let result = input.all_states_declared_and_used();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNDECLARED_STATE_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNDECLARED_STATE);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -334,7 +412,7 @@ mod tests {
|
|
|
|
|
|
let result = input.all_states_declared_and_used();
|
|
let result = input.all_states_declared_and_used();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNUSED_STATE_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNUSED_STATE);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -346,6 +424,61 @@ mod tests {
|
|
assert_ok(result);
|
|
assert_ok(result);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ #[test]
|
|
|
|
+ fn receivers_and_senders_call_msg_reused_for_non_call_ok() {
|
|
|
|
+ let input = ProtocolModel::new(Protocol::new(
|
|
|
|
+ NameDef::new("OwnedTypes"),
|
|
|
|
+ [
|
|
|
|
+ ActorDef::new("server", ["Listening"]),
|
|
|
|
+ ActorDef::new("client", ["Client"]),
|
|
|
|
+ ActorDef::new("file", ["FileInit", "Opened"]),
|
|
|
|
+ ActorDef::new("file_handle", ["FileHandle"]),
|
|
|
|
+ ],
|
|
|
|
+ [
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("Client", []),
|
|
|
|
+ None,
|
|
|
|
+ [
|
|
|
|
+ State::new("Client", []),
|
|
|
|
+ State::new("FileHandle", ["Opened"]),
|
|
|
|
+ ],
|
|
|
|
+ [Dest::new(
|
|
|
|
+ DestinationState::Service(State::new("Listening", [])),
|
|
|
|
+ Message::new("Open", false, []),
|
|
|
|
+ )],
|
|
|
|
+ ),
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("Listening", []),
|
|
|
|
+ Some(Message::new("Open", false, [])),
|
|
|
|
+ [State::new("Listening", []), State::new("FileInit", [])],
|
|
|
|
+ [
|
|
|
|
+ Dest::new(
|
|
|
|
+ DestinationState::Individual(State::new("Client", [])),
|
|
|
|
+ Message::new("Open", true, ["Opened"]),
|
|
|
|
+ ),
|
|
|
|
+ // Note that the same "Open" message is being used here, but that the
|
|
|
|
+ // FileInit state does not send a reply. This should be allowed.
|
|
|
|
+ Dest::new(
|
|
|
|
+ DestinationState::Individual(State::new("FileInit", [])),
|
|
|
|
+ Message::new("Open", false, []),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("FileInit", []),
|
|
|
|
+ Some(Message::new("Open", false, [])),
|
|
|
|
+ [State::new("Opened", [])],
|
|
|
|
+ [],
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ))
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ let result = input.receivers_and_senders_matched();
|
|
|
|
+
|
|
|
|
+ assert_ok(result);
|
|
|
|
+ }
|
|
|
|
+
|
|
#[test]
|
|
#[test]
|
|
fn receivers_and_senders_matched_unmatched_sender_err() {
|
|
fn receivers_and_senders_matched_unmatched_sender_err() {
|
|
let input = ProtocolModel::new(Protocol::new(
|
|
let input = ProtocolModel::new(Protocol::new(
|
|
@@ -365,7 +498,7 @@ mod tests {
|
|
|
|
|
|
let result = input.receivers_and_senders_matched();
|
|
let result = input.receivers_and_senders_matched();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNMATCHED_SENDER_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNMATCHED_OUTGOING);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -384,7 +517,58 @@ mod tests {
|
|
|
|
|
|
let result = input.receivers_and_senders_matched();
|
|
let result = input.receivers_and_senders_matched();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNMATCHED_RECEIVER_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNMATCHED_INCOMING);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn receivers_and_senders_matched_servers_must_explicitly_receive_replies_err() {
|
|
|
|
+ // Only client actors are allowed to implicitly receive replies.
|
|
|
|
+ let input = ProtocolModel::new(Protocol::new(
|
|
|
|
+ NameDef::new("Conversation"),
|
|
|
|
+ [
|
|
|
|
+ ActorDef::new("alice", ["Alice"]),
|
|
|
|
+ ActorDef::new("bob", ["Bob"]),
|
|
|
|
+ ],
|
|
|
|
+ [
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("Alice", []),
|
|
|
|
+ None,
|
|
|
|
+ [State::new("Alice", [])],
|
|
|
|
+ [Dest::new(
|
|
|
|
+ DestinationState::Service(State::new("Bob", [])),
|
|
|
|
+ Message::new("Greeting", false, []),
|
|
|
|
+ )],
|
|
|
|
+ ),
|
|
|
|
+ // Notice that because Bob only has transitions which handle messages, bob is a
|
|
|
|
+ // server actor.
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("Bob", []),
|
|
|
|
+ Some(Message::new("Greeting", false, [])),
|
|
|
|
+ [State::new("Bob", [])],
|
|
|
|
+ [Dest::new(
|
|
|
|
+ DestinationState::Individual(State::new("Alice", [])),
|
|
|
|
+ Message::new("Query", false, []),
|
|
|
|
+ )],
|
|
|
|
+ ),
|
|
|
|
+ // Alice is sending a Query::Reply to Bob, but because he does not have a
|
|
|
|
+ // transition which accepts that message type, this will be an unmatched outgoing
|
|
|
|
+ // error.
|
|
|
|
+ Transition::new(
|
|
|
|
+ State::new("Alice", []),
|
|
|
|
+ Some(Message::new("Query", false, [])),
|
|
|
|
+ [State::new("End", [])],
|
|
|
|
+ [Dest::new(
|
|
|
|
+ DestinationState::Individual(State::new("Bob", [])),
|
|
|
|
+ Message::new("Query", true, []),
|
|
|
|
+ )],
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ))
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ let result = input.receivers_and_senders_matched();
|
|
|
|
+
|
|
|
|
+ assert_err(result, error::msgs::UNMATCHED_OUTGOING);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -481,7 +665,7 @@ mod tests {
|
|
|
|
|
|
let result = input.no_undeliverable_msgs();
|
|
let result = input.no_undeliverable_msgs();
|
|
|
|
|
|
- assert_err(result, error::msgs::UNDELIVERABLE_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::UNDELIVERABLE);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -525,7 +709,7 @@ mod tests {
|
|
|
|
|
|
let result = input.replies_expected();
|
|
let result = input.replies_expected();
|
|
|
|
|
|
- assert_err(result, error::msgs::INVALID_REPLY_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::INVALID_REPLY);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -553,7 +737,7 @@ mod tests {
|
|
|
|
|
|
let result = input.replies_expected();
|
|
let result = input.replies_expected();
|
|
|
|
|
|
- assert_err(result, error::msgs::MULTIPLE_REPLIES_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::MULTIPLE_REPLIES);
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
@@ -655,6 +839,6 @@ mod tests {
|
|
|
|
|
|
let result = input.clients_only_receive_replies();
|
|
let result = input.clients_only_receive_replies();
|
|
|
|
|
|
- assert_err(result, error::msgs::CLIENT_RECEIVED_NON_REPLY_ERR);
|
|
|
|
|
|
+ assert_err(result, error::msgs::CLIENT_RECEIVED_NON_REPLY);
|
|
}
|
|
}
|
|
}
|
|
}
|