validation.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. use std::{collections::HashSet, hash::Hash};
  2. use proc_macro2::{Ident, Span};
  3. use btrun::End;
  4. use crate::{
  5. error::{self, MaybeErr},
  6. model::{MsgInfo, ProtocolModel},
  7. parsing::{DestinationState, GetSpan, State},
  8. };
  9. impl ProtocolModel {
  10. #[allow(dead_code)]
  11. pub(crate) fn validate(&self) -> syn::Result<()> {
  12. self.all_states_declared_and_used()
  13. .combine(self.receivers_and_senders_matched())
  14. .combine(self.no_undeliverable_msgs())
  15. .combine(self.replies_expected())
  16. .combine(self.no_unobservable_states())
  17. .into()
  18. }
  19. /// Verifies that every state which is declared is actually used.
  20. fn all_states_declared_and_used(&self) -> MaybeErr {
  21. let end = Ident::new(End::ident(), Span::call_site());
  22. let mut declared: HashSet<&Ident> = HashSet::new();
  23. declared.insert(&end);
  24. for actor_def in self.def().actor_defs.iter() {
  25. for state in actor_def.states.as_ref().iter() {
  26. declared.insert(state);
  27. }
  28. }
  29. let mut used: HashSet<&Ident> = HashSet::with_capacity(declared.len());
  30. for transition in self.def().transitions.iter() {
  31. let in_state = &transition.in_state;
  32. used.insert(&in_state.state_trait);
  33. used.extend(in_state.owned_states().map(|ident| ident.as_ref()));
  34. if let Some(in_msg) = transition.in_msg() {
  35. used.extend(in_msg.owned_states().map(|ident| ident.as_ref()));
  36. }
  37. for out_states in transition.out_states.as_ref().iter() {
  38. used.insert(&out_states.state_trait);
  39. used.extend(out_states.owned_states().map(|ident| ident.as_ref()));
  40. }
  41. // We don't have to check the states referred to in out_msgs because the
  42. // receivers_and_senders_matched method ensures that each of these exists in a receiver
  43. // position.
  44. }
  45. let undeclared: MaybeErr = used
  46. .difference(&declared)
  47. .map(|ident| syn::Error::new(ident.span(), error::msgs::UNDECLARED_STATE))
  48. .collect();
  49. let unused: MaybeErr = declared
  50. .difference(&used)
  51. .filter(|ident| **ident != End::ident())
  52. .map(|ident| syn::Error::new(ident.span(), error::msgs::UNUSED_STATE))
  53. .collect();
  54. undeclared.combine(unused)
  55. }
  56. /// Ensures that the recipient state for every sent message has a receiving transition
  57. /// defined, and every receiver has a sender. Note that each message isn't required to have a
  58. /// unique sender or a unique receiver, just that at least one of each much be defined.
  59. fn receivers_and_senders_matched<'s>(&'s self) -> MaybeErr {
  60. /// Represents a message sender or receiver.
  61. ///
  62. /// This type is essentially just a tuple of references, but was created so a [Hash]
  63. /// implementation could be defined.
  64. #[cfg_attr(test, derive(Debug))]
  65. struct MsgEndpoint<'a> {
  66. state: &'a State,
  67. msg_info: &'a MsgInfo,
  68. }
  69. impl<'a> MsgEndpoint<'a> {
  70. fn new(state: &'a State, msg_info: &'a MsgInfo) -> Self {
  71. Self { state, msg_info }
  72. }
  73. }
  74. impl<'a> PartialEq for MsgEndpoint<'a> {
  75. fn eq(&self, other: &Self) -> bool {
  76. self.state.state_trait == other.state.state_trait
  77. && self.msg_info.def().msg_type == self.msg_info.def().msg_type
  78. && self.msg_info.is_reply() == self.msg_info.is_reply()
  79. }
  80. }
  81. impl<'a> Eq for MsgEndpoint<'a> {}
  82. impl<'a> Hash for MsgEndpoint<'a> {
  83. fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
  84. self.state.state_trait.hash(state);
  85. self.msg_info.def().msg_type.hash(state);
  86. self.msg_info.is_reply().hash(state);
  87. }
  88. }
  89. #[cfg(test)]
  90. impl<'a> std::fmt::Display for MsgEndpoint<'a> {
  91. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  92. write!(
  93. f,
  94. "({}, {})",
  95. self.state.state_trait,
  96. self.msg_info.msg_name()
  97. )
  98. }
  99. }
  100. let msgs = self.msg_lookup();
  101. let mut outgoing: HashSet<MsgEndpoint<'s>> = HashSet::new();
  102. let mut incoming: HashSet<MsgEndpoint<'s>> = HashSet::new();
  103. for actor in self.actors_iter() {
  104. let methods = actor
  105. .states()
  106. .values()
  107. .flat_map(|state| state.methods().values());
  108. for method in methods {
  109. let transition = method.def();
  110. if let Some(msg) = transition.in_msg() {
  111. let msg_info = msgs.lookup(msg);
  112. incoming.insert(MsgEndpoint::new(&transition.in_state, msg_info));
  113. }
  114. for dest in transition.out_msgs.as_ref().iter() {
  115. let dest_state = match &dest.state {
  116. DestinationState::Individual(dest_state) => dest_state,
  117. DestinationState::Service(dest_state) => dest_state,
  118. };
  119. let msg_info = self.msg_lookup().lookup(&dest.msg);
  120. outgoing.insert(MsgEndpoint::new(dest_state, msg_info));
  121. if actor.is_client() {
  122. if let Some(reply) = msg_info.reply() {
  123. incoming.insert(MsgEndpoint::new(&transition.in_state, reply));
  124. }
  125. }
  126. }
  127. }
  128. }
  129. let extra_senders: MaybeErr = outgoing
  130. .difference(&incoming)
  131. .map(|endpoint| {
  132. syn::Error::new(
  133. endpoint.msg_info.def().span(),
  134. error::msgs::UNMATCHED_OUTGOING,
  135. )
  136. })
  137. .collect();
  138. let extra_receivers: MaybeErr = incoming
  139. .difference(&outgoing)
  140. .map(|endpoint| {
  141. syn::Error::new(
  142. endpoint.msg_info.def().span(),
  143. error::msgs::UNMATCHED_INCOMING,
  144. )
  145. })
  146. .collect();
  147. extra_senders.combine(extra_receivers)
  148. }
  149. /// Checks that messages are only sent to destinations which are either services, states
  150. /// which are owned by the sender, listed in the output states, or that the message is a
  151. /// reply.
  152. fn no_undeliverable_msgs(&self) -> MaybeErr {
  153. let mut err = MaybeErr::none();
  154. for transition in self.def().transitions.iter() {
  155. let mut allowed_states: Option<HashSet<&Ident>> = None;
  156. for dest in transition.out_msgs.as_ref().iter() {
  157. if dest.msg.is_reply() {
  158. continue;
  159. }
  160. match &dest.state {
  161. DestinationState::Service(_) => continue,
  162. DestinationState::Individual(dest_state) => {
  163. let owned_states = transition
  164. .in_state
  165. .owned_states()
  166. .map(|ident| ident.as_ref());
  167. let allowed = allowed_states.get_or_insert_with(|| {
  168. transition
  169. .out_states
  170. .as_ref()
  171. .iter()
  172. .map(|state| state.state_trait.as_ref())
  173. .chain(owned_states)
  174. .collect()
  175. });
  176. if !allowed.contains(dest_state.state_trait.as_ref()) {
  177. err = err.combine(
  178. syn::Error::new(
  179. dest_state.state_trait.span(),
  180. error::msgs::UNDELIVERABLE,
  181. )
  182. .into(),
  183. );
  184. }
  185. }
  186. }
  187. }
  188. }
  189. err
  190. }
  191. /// Verifies that exactly one reply is sent in response to a previously sent message.
  192. fn replies_expected(&self) -> MaybeErr {
  193. let mut err = MaybeErr::none();
  194. for transition in self.def().transitions.iter() {
  195. let replies: Vec<_> = transition
  196. .out_msgs
  197. .as_ref()
  198. .iter()
  199. .map(|dest| &dest.msg)
  200. .filter(|msg| msg.is_reply())
  201. .collect();
  202. if replies.is_empty() {
  203. continue;
  204. }
  205. if replies.len() > 1 {
  206. err = err.combine(
  207. replies
  208. .iter()
  209. .map(|reply| {
  210. syn::Error::new(reply.msg_type.span(), error::msgs::MULTIPLE_REPLIES)
  211. })
  212. .collect(),
  213. );
  214. }
  215. if transition.in_msg().is_none() {
  216. err = err.combine(
  217. replies
  218. .iter()
  219. .map(|reply| {
  220. syn::Error::new(reply.msg_type.span(), error::msgs::INVALID_REPLY)
  221. })
  222. .collect(),
  223. );
  224. }
  225. }
  226. err
  227. }
  228. /// Checks that there are no client states which are only receiving replies. Such states can't
  229. /// be observed, because the methods which sent the original messages will return their replies.
  230. fn no_unobservable_states(&self) -> MaybeErr {
  231. self.actors_iter()
  232. .filter(|actor| actor.is_client())
  233. .flat_map(|actor| actor.states().values())
  234. .filter(|state| {
  235. state.methods().values().all(|method| {
  236. if let Some(in_msg) = method.def().in_msg() {
  237. in_msg.is_reply()
  238. } else {
  239. false
  240. }
  241. })
  242. })
  243. .map(|state| syn::Error::new(state.span(), error::msgs::UNOBSERVABLE_STATE))
  244. .collect()
  245. }
  246. }
  247. #[cfg(test)]
  248. mod tests {
  249. use super::*;
  250. use crate::{
  251. error::{assert_err, assert_ok},
  252. parsing::{ActorDef, Dest, Message, NameDef, Protocol, Transition},
  253. };
  254. #[test]
  255. fn all_states_declared_and_used_ok() {
  256. let input = ProtocolModel::new(Protocol::minimal()).unwrap();
  257. let result = input.all_states_declared_and_used();
  258. assert_ok(result);
  259. }
  260. #[test]
  261. fn all_states_declared_and_used_end_not_used_ok() {
  262. const STATE_NAME: &str = "Init";
  263. let input = ProtocolModel::new(Protocol::new(
  264. NameDef::new("Test"),
  265. [ActorDef::new("actor", [STATE_NAME])],
  266. [Transition::new(
  267. State::new(STATE_NAME, []),
  268. Some(Message::new("Activate", false, [])),
  269. [State::new(STATE_NAME, [])],
  270. [],
  271. )],
  272. ))
  273. .unwrap();
  274. let result = input.all_states_declared_and_used();
  275. assert_ok(result);
  276. }
  277. #[test]
  278. fn all_states_declared_and_used_undeclared_err() {
  279. let input = ProtocolModel::new(Protocol::new(
  280. NameDef::new("Undeclared"),
  281. [ActorDef::new("actor", ["Init"])],
  282. [Transition::new(
  283. State::new("Init", []),
  284. Some(Message::new("Activate", false, [])),
  285. [State::new("Next", [])],
  286. [],
  287. )],
  288. ))
  289. .unwrap();
  290. let result = input.all_states_declared_and_used();
  291. assert_err(result, error::msgs::UNDECLARED_STATE);
  292. }
  293. #[test]
  294. fn all_states_declared_and_used_undeclared_out_state_owned_err() {
  295. let input = ProtocolModel::new(Protocol::new(
  296. NameDef::new("Undeclared"),
  297. [ActorDef::new("actor", ["Init", "Next"])],
  298. [Transition::new(
  299. State::new("Init", []),
  300. Some(Message::new("Activate", false, [])),
  301. [State::new("Init", []), State::new("Next", ["Undeclared"])],
  302. [],
  303. )],
  304. ))
  305. .unwrap();
  306. let result = input.all_states_declared_and_used();
  307. assert_err(result, error::msgs::UNDECLARED_STATE);
  308. }
  309. #[test]
  310. fn all_states_declared_and_used_undeclared_in_state_owned_err() {
  311. let input = ProtocolModel::new(Protocol::new(
  312. NameDef::new("Undeclared"),
  313. [ActorDef::new("actor", ["Init", "Next"])],
  314. [Transition::new(
  315. State::new("Init", ["Undeclared"]),
  316. Some(Message::new("Activate", false, [])),
  317. [State::new("Next", [])],
  318. [],
  319. )],
  320. ))
  321. .unwrap();
  322. let result = input.all_states_declared_and_used();
  323. assert_err(result, error::msgs::UNDECLARED_STATE);
  324. }
  325. #[test]
  326. fn all_states_declared_and_used_unused_err() {
  327. let input = ProtocolModel::new(Protocol::new(
  328. NameDef::new("Unused"),
  329. [ActorDef::new("actor", ["Init", "Extra"])],
  330. [Transition::new(
  331. State::new("Init", []),
  332. Some(Message::new("Activate", false, [])),
  333. [State::new("End", [])],
  334. [],
  335. )],
  336. ))
  337. .unwrap();
  338. let result = input.all_states_declared_and_used();
  339. assert_err(result, error::msgs::UNUSED_STATE);
  340. }
  341. #[test]
  342. fn receivers_and_senders_matched_ok() {
  343. let input = ProtocolModel::new(Protocol::minimal()).unwrap();
  344. let result = input.receivers_and_senders_matched();
  345. assert_ok(result);
  346. }
  347. #[test]
  348. fn receivers_and_senders_call_msg_reused_for_non_call_ok() {
  349. let input = ProtocolModel::new(Protocol::new(
  350. NameDef::new("OwnedTypes"),
  351. [
  352. ActorDef::new("server", ["Listening"]),
  353. ActorDef::new("client", ["Client"]),
  354. ActorDef::new("file", ["FileInit", "Opened"]),
  355. ActorDef::new("file_handle", ["FileHandle"]),
  356. ],
  357. [
  358. Transition::new(
  359. State::new("Client", []),
  360. None,
  361. [
  362. State::new("Client", []),
  363. State::new("FileHandle", ["Opened"]),
  364. ],
  365. [Dest::new(
  366. DestinationState::Service(State::new("Listening", [])),
  367. Message::new("Open", false, []),
  368. )],
  369. ),
  370. Transition::new(
  371. State::new("Listening", []),
  372. Some(Message::new("Open", false, [])),
  373. [State::new("Listening", []), State::new("FileInit", [])],
  374. [
  375. Dest::new(
  376. DestinationState::Individual(State::new("Client", [])),
  377. Message::new("Open", true, ["Opened"]),
  378. ),
  379. // Note that the same "Open" message is being used here, but that the
  380. // FileInit state does not send a reply. This should be allowed.
  381. Dest::new(
  382. DestinationState::Individual(State::new("FileInit", [])),
  383. Message::new("Open", false, []),
  384. ),
  385. ],
  386. ),
  387. Transition::new(
  388. State::new("FileInit", []),
  389. Some(Message::new("Open", false, [])),
  390. [State::new("Opened", [])],
  391. [],
  392. ),
  393. ],
  394. ))
  395. .unwrap();
  396. let result = input.receivers_and_senders_matched();
  397. assert_ok(result);
  398. }
  399. #[test]
  400. fn receivers_and_senders_matched_unmatched_sender_err() {
  401. let input = ProtocolModel::new(Protocol::new(
  402. NameDef::new("Unbalanced"),
  403. [ActorDef::new("actor", ["Init"])],
  404. [Transition::new(
  405. State::new("Init", []),
  406. None,
  407. [State::new("Init", [])],
  408. [Dest::new(
  409. DestinationState::Service(State::new("Init", [])),
  410. Message::new("Msg", false, []),
  411. )],
  412. )],
  413. ))
  414. .unwrap();
  415. let result = input.receivers_and_senders_matched();
  416. assert_err(result, error::msgs::UNMATCHED_OUTGOING);
  417. }
  418. #[test]
  419. fn receivers_and_senders_matched_unmatched_receiver_err() {
  420. let input = ProtocolModel::new(Protocol::new(
  421. NameDef::new("Unbalanced"),
  422. [ActorDef::new("actor", ["Init"])],
  423. [Transition::new(
  424. State::new("Init", []),
  425. Some(Message::new("NotExists", false, [])),
  426. [State::new("Init", [])],
  427. [],
  428. )],
  429. ))
  430. .unwrap();
  431. let result = input.receivers_and_senders_matched();
  432. assert_err(result, error::msgs::UNMATCHED_INCOMING);
  433. }
  434. #[test]
  435. fn receivers_and_senders_matched_servers_must_explicitly_receive_replies_err() {
  436. // Only client actors are allowed to implicitly receive replies.
  437. let input = ProtocolModel::new(Protocol::new(
  438. NameDef::new("Conversation"),
  439. [
  440. ActorDef::new("alice", ["Alice"]),
  441. ActorDef::new("bob", ["Bob"]),
  442. ],
  443. [
  444. Transition::new(
  445. State::new("Alice", []),
  446. None,
  447. [State::new("Alice", [])],
  448. [Dest::new(
  449. DestinationState::Service(State::new("Bob", [])),
  450. Message::new("Greeting", false, []),
  451. )],
  452. ),
  453. // Notice that because Bob only has transitions which handle messages, bob is a
  454. // server actor.
  455. Transition::new(
  456. State::new("Bob", []),
  457. Some(Message::new("Greeting", false, [])),
  458. [State::new("Bob", [])],
  459. [Dest::new(
  460. DestinationState::Individual(State::new("Alice", [])),
  461. Message::new("Query", false, []),
  462. )],
  463. ),
  464. // Alice is sending a Query::Reply to Bob, but because he does not have a
  465. // transition which accepts that message type, this will be an unmatched outgoing
  466. // error.
  467. Transition::new(
  468. State::new("Alice", []),
  469. Some(Message::new("Query", false, [])),
  470. [State::new("End", [])],
  471. [Dest::new(
  472. DestinationState::Individual(State::new("Bob", [])),
  473. Message::new("Query", true, []),
  474. )],
  475. ),
  476. ],
  477. ))
  478. .unwrap();
  479. let result = input.receivers_and_senders_matched();
  480. assert_err(result, error::msgs::UNMATCHED_OUTGOING);
  481. }
  482. #[test]
  483. fn no_undeliverable_msgs_ok() {
  484. let input = ProtocolModel::new(Protocol::minimal()).unwrap();
  485. let result = input.no_undeliverable_msgs();
  486. assert_ok(result);
  487. }
  488. #[test]
  489. fn no_undeliverable_msgs_reply_ok() {
  490. let input = ProtocolModel::new(Protocol::new(
  491. NameDef::new("Undeliverable"),
  492. [ActorDef::new("actor", ["Listening", "Client"])],
  493. [Transition::new(
  494. State::new("Listening", []),
  495. Some(Message::new("Msg", false, [])),
  496. [State::new("Listening", [])],
  497. [Dest::new(
  498. DestinationState::Individual(State::new("Client", [])),
  499. Message::new("Msg", true, []),
  500. )],
  501. )],
  502. ))
  503. .unwrap();
  504. let result = input.no_undeliverable_msgs();
  505. assert_ok(result);
  506. }
  507. #[test]
  508. fn no_undeliverable_msgs_service_ok() {
  509. let input = ProtocolModel::new(Protocol::new(
  510. NameDef::new("Undeliverable"),
  511. [ActorDef::new("actor", ["Client", "Server"])],
  512. [Transition::new(
  513. State::new("Client", []),
  514. None,
  515. [State::new("Client", [])],
  516. [Dest::new(
  517. DestinationState::Service(State::new("Server", [])),
  518. Message::new("Msg", false, []),
  519. )],
  520. )],
  521. ))
  522. .unwrap();
  523. let result = input.no_undeliverable_msgs();
  524. assert_ok(result);
  525. }
  526. #[test]
  527. fn no_undeliverable_msgs_owned_ok() {
  528. let input = ProtocolModel::new(Protocol::new(
  529. NameDef::new("Undeliverable"),
  530. [ActorDef::new("actor", ["FileClient", "FileHandle"])],
  531. [Transition::new(
  532. State::new("FileClient", ["FileHandle"]),
  533. None,
  534. [State::new("FileClient", [])],
  535. [Dest::new(
  536. DestinationState::Individual(State::new("FileHandle", [])),
  537. Message::new("FileOp", false, []),
  538. )],
  539. )],
  540. ))
  541. .unwrap();
  542. let result = input.no_undeliverable_msgs();
  543. assert_ok(result);
  544. }
  545. #[test]
  546. fn no_undeliverable_msgs_err() {
  547. let input = ProtocolModel::new(Protocol::new(
  548. NameDef::new("Undeliverable"),
  549. [ActorDef::new("actor", ["Client", "Server"])],
  550. [Transition::new(
  551. State::new("Client", []),
  552. None,
  553. [State::new("Client", [])],
  554. [Dest::new(
  555. DestinationState::Individual(State::new("Server", [])),
  556. Message::new("Msg", false, []),
  557. )],
  558. )],
  559. ))
  560. .unwrap();
  561. let result = input.no_undeliverable_msgs();
  562. assert_err(result, error::msgs::UNDELIVERABLE);
  563. }
  564. #[test]
  565. fn replies_expected_ok() {
  566. let input = ProtocolModel::new(Protocol::new(
  567. NameDef::new("ValidReplies"),
  568. [ActorDef::new("actor", ["Client", "Server"])],
  569. [Transition::new(
  570. State::new("Server", []),
  571. Some(Message::new("Msg", false, [])),
  572. [State::new("Server", [])],
  573. [Dest::new(
  574. DestinationState::Individual(State::new("Client", [])),
  575. Message::new("Msg", true, []),
  576. )],
  577. )],
  578. ))
  579. .unwrap();
  580. let result = input.replies_expected();
  581. assert_ok(result);
  582. }
  583. #[test]
  584. fn replies_expected_invalid_reply_err() {
  585. let input = ProtocolModel::new(Protocol::new(
  586. NameDef::new("ValidReplies"),
  587. [ActorDef::new("actor", ["Client", "Server"])],
  588. [Transition::new(
  589. State::new("Client", []),
  590. None,
  591. [State::new("Client", [])],
  592. [Dest::new(
  593. DestinationState::Individual(State::new("Server", [])),
  594. Message::new("Msg", true, []),
  595. )],
  596. )],
  597. ))
  598. .unwrap();
  599. let result = input.replies_expected();
  600. assert_err(result, error::msgs::INVALID_REPLY);
  601. }
  602. #[test]
  603. fn replies_expected_multiple_replies_err() {
  604. let input = ProtocolModel::new(Protocol::new(
  605. NameDef::new("ValidReplies"),
  606. [ActorDef::new("actor", ["Client", "OtherClient", "Server"])],
  607. [Transition::new(
  608. State::new("Server", []),
  609. Some(Message::new("Msg", false, [])),
  610. [State::new("Server", [])],
  611. [
  612. Dest::new(
  613. DestinationState::Individual(State::new("Client", [])),
  614. Message::new("Msg", true, []),
  615. ),
  616. Dest::new(
  617. DestinationState::Individual(State::new("OtherClient", [])),
  618. Message::new("Msg", true, []),
  619. ),
  620. ],
  621. )],
  622. ))
  623. .unwrap();
  624. let result = input.replies_expected();
  625. assert_err(result, error::msgs::MULTIPLE_REPLIES);
  626. }
  627. }