A C++ toolbox repo until the pair uTL/dTL arives
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

445 lines
18 KiB

  1. /*!
  2. * \file utils/sequencer.h
  3. * \brief
  4. * A terminal-like device communication automation tool
  5. *
  6. * \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
  7. *
  8. * <dl class=\"section copyright\"><dt>License</dt><dd>
  9. * The MIT License (MIT)
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in all
  19. * copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  27. * SOFTWARE.
  28. * </dd></dl>
  29. */
  30. #ifndef TBX_UTILS_SEQUENCER_H_
  31. #define TBX_UTILS_SEQUENCER_H_
  32. #include <core/core.h>
  33. #include <core/crtp.h>
  34. #include <cont/range.h>
  35. #include <ctime>
  36. #include <array>
  37. #include <string_view>
  38. #include <limits>
  39. #include <type_traits>
  40. #include <functional>
  41. namespace tbx {
  42. /*!
  43. * \class sequencer
  44. * \brief
  45. * A CRTP base class to provide the sequencer functionality.
  46. *
  47. * Sequencer can automate communication with a terminal-like device such as AT-command modems etc...
  48. * It can operate based on a script array and handle the outgoing commands and incoming responses.
  49. * The user can create matching rules on received data and hook handlers and actions on them.
  50. *
  51. * The derived class (implementation) has to provide:
  52. * 1) size_t get(Data_t* data);
  53. * This function return 0 or a number of Data_t items. The data points to buffer for the input data.
  54. * 2) size_t put(const Data_t* data, size_t n);
  55. * This function sends to implementation the data pointed by \c data witch have size \c n.
  56. * 3) clock_t clock();
  57. * This function return a number to be used as time. The units of this function may be arbitrary but they
  58. * match the units in \c record_t::timeout field.
  59. *
  60. * \tparam Impl_t The type of derived class
  61. * \tparam Cont_t The container type holding the data of type \c Data_t for the derived class.
  62. * \tparam Data_t The char-like stream item type. Usually \c char
  63. * \tparam N The size of the sequence buffer to temporary store each line from get().
  64. *
  65. * \note
  66. * We need access to derived class container to sneaky get a range of the data beside
  67. * the normal data flow, in order to implement the \see control_t::DETECT operation.
  68. */
  69. template <typename Impl_t, typename Cont_t, typename Data_t, size_t N>
  70. class sequencer {
  71. _CRTP_IMPL(Impl_t);
  72. static_assert(
  73. std::is_same_v<typename Cont_t::value_type, Data_t>,
  74. "Cont_t must be a container of type Data_t"
  75. );
  76. // local type dispatch
  77. using str_view_t = std::basic_string_view<Data_t>;
  78. using range_t = typename Cont_t::range_t;
  79. //! \name Public types
  80. //! @{
  81. public:
  82. //! \enum status_t
  83. //! \brief The sequencer run status
  84. enum class status_t {
  85. OK, ERROR
  86. };
  87. //! \enum action_t
  88. //! \brief Possible response actions for the sequencer
  89. enum class action_t {
  90. NO, NEXT, GOTO, EXIT_OK, EXIT_ERROR
  91. };
  92. //! \enum control_t
  93. //! \brief The control type of the script entry.
  94. enum class control_t {
  95. NOP, //!< No command, dont send or expect anything, used for delays
  96. SEND, //!< Send data to implementation through put()
  97. EXPECT, //!< Expects data from implementation via get()
  98. DETECT //!< Detects data into rx buffer without receiving them via contents()
  99. //! \note
  100. //! The \c DETECT extra incoming channel serve the purpose of sneak into receive
  101. //! buffer and check for data without getting them. This is useful when the receive driver
  102. //! is buffered with a delimiter and we seek for data that don't follow the delimiter pattern.
  103. //!
  104. //! For example:
  105. //! A modem sends reponses with '\n' termination but for some "special" command it opens a cursor
  106. //! lets say ">$ " without '\n' at the end.
  107. };
  108. //! \enum match_t
  109. //! \brief Token match types
  110. enum class match_t {
  111. NO, STARTS_WITH, ENDS_WITH, CONTAINS, nSTARTS_WITH, nENDS_WITH, nCONTAINS
  112. };
  113. /*!
  114. * Match handler function pointer type.
  115. * Expects a pointer to buffer and a size and returns status
  116. */
  117. using handler_ft = void (*) (const Data_t*, size_t);
  118. /*!
  119. * \struct handle_t
  120. * \brief
  121. * The script record handle block.
  122. *
  123. * Each script record contains some blocks for matching functionality. Each block
  124. * has a token and a matching type. If the response matches the token, the sequencer calls
  125. * the handler and perform the action.
  126. */
  127. struct handle_t {
  128. std::basic_string_view<Data_t>
  129. token; //!< The token for the match
  130. match_t match_type; //!< The matching type functionality
  131. handler_ft handler; //!< The handler to called if the match is successful.
  132. action_t action; //!< The action to be performer if the match is successful
  133. size_t idx; //!< The index for the action_t::GOTO action. Otherwise can be left 0.
  134. };
  135. /*!
  136. * \struct record_t
  137. * \brief
  138. * Describes the sequencer's script record entry (line).
  139. *
  140. * Each line consist from a control, 2 blocks and a timeout. The control says if we send or receive data.
  141. * The blocks contain the data and the matching information. And the timeout guards the entire line.
  142. */
  143. template<size_t Nhandles =2>
  144. struct record_t {
  145. control_t control; //!< The type of the entry
  146. std::array<handle_t, Nhandles>
  147. block; //!< The matching blocks
  148. clock_t timeout; //!< Timeout in CPU time
  149. };
  150. /*!
  151. * \struct script_t
  152. * \brief
  153. * Describes the sequencer's script.
  154. *
  155. * The user can create arrays as the example bellow to act as a script.
  156. * \code
  157. * const std::array<Seq::record_t, 8> script = {{
  158. * / * 0 * / {Seq::control_t::NOP, {"", Seq::match_t::NO, nullptr, Seq::action_t::GOTO, 1}, 1000}, //delay 1000 clocks
  159. * / * 1 * / {Seq::control_t::SEND, {"ATE0\r\n", Seq::match_t::NO, nullptr, Seq::action_t::NEXT, 0}, 1000},
  160. * / * 2 * / {Seq::control_t::EXPECT, {{
  161. * {"OK\r\n", Seq::match_t::ENDS_WITH, nullptr, Seq::action_t::NEXT, 0},
  162. * {"ERROR", Seq::match_t::CONTAINS, nullptr, Seq::action_t::EXIT_ERROR, 0} }},
  163. * 1000
  164. * },
  165. * // ...
  166. * }};
  167. * \endcode
  168. */
  169. template <size_t Nrecords, size_t Nhandles =2>
  170. using script_t = std::array<record_t<Nhandles>, Nrecords>;
  171. //! @}
  172. //! \name Constructor / Destructor
  173. //!@{
  174. protected:
  175. ~sequencer () = default; //!< \brief Allow destructor from derived only
  176. sequencer () = default; //!< \brief A default constructor from derived only
  177. sequencer(const sequencer&) = delete; //!< No copies
  178. sequencer& operator= (const sequencer&) = delete; //!< No copy assignments
  179. //!@}
  180. //! \name Sequencer interface requirements for implementer
  181. //! @{
  182. private:
  183. size_t get_ (Data_t* data) { return impl().get (data); }
  184. size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); }
  185. const range_t contents_ () const { return impl().contents(); }
  186. clock_t clock_ () { return impl().clock(); }
  187. //! @}
  188. //! \name Private functionality
  189. //! @{
  190. private:
  191. /*!
  192. * \brief
  193. * Check if the \c stream starts with the \c prefix
  194. * \param stream The stream in witch we search
  195. * \param prefix What we search
  196. * \return True on success, false otherwise
  197. */
  198. static bool starts_with_ (const str_view_t stream, const str_view_t prefix) {
  199. return (stream.rfind(prefix, 0) != str_view_t::npos);
  200. }
  201. /*!
  202. * \brief
  203. * Check if the \c stream ends with the \c postfix
  204. * \param stream The stream in witch we search
  205. * \param postfix What we search
  206. * \return True on success, false otherwise
  207. */
  208. static bool ends_with_ (const str_view_t stream, const str_view_t postfix) {
  209. if (stream.size() < postfix.size())
  210. return false;
  211. return (
  212. stream.compare(
  213. stream.size() - postfix.size(),
  214. postfix.size(),
  215. postfix) == 0
  216. );
  217. }
  218. /*!
  219. * \brief
  220. * Check if the \c haystack contains the \c needle
  221. * \param haystack The stream in witch we search
  222. * \param needle What we search
  223. * \return True on success, false otherwise
  224. */
  225. static bool contains_ (const str_view_t haystack, const str_view_t needle) {
  226. return (haystack.find(needle) != str_view_t::npos);
  227. }
  228. /*!
  229. * \brief
  230. * Return the new sequencer's step value.
  231. *
  232. * Step is index to the sequencer's script array.
  233. *
  234. * \param current_idx The current step value
  235. * \param action The advancing type
  236. * \param go_idx The new value of the step in the case of GOTO type
  237. * \return The new sequencer's step value
  238. */
  239. static size_t step_ (size_t current_idx, action_t action, size_t go_idx =0) {
  240. switch (action) {
  241. default:
  242. case action_t::NO: return current_idx;
  243. case action_t::NEXT: return ++current_idx;
  244. case action_t::GOTO: return go_idx;
  245. case action_t::EXIT_OK:
  246. case action_t::EXIT_ERROR:
  247. return 0;
  248. }
  249. }
  250. static status_t action_ (size_t& step, const handle_t& block, const str_view_t buffer = str_view_t{}) {
  251. if (block.handler != nullptr)
  252. block.handler(buffer.begin(), buffer.size());
  253. switch (block.action) {
  254. case action_t::EXIT_OK:
  255. step = std::numeric_limits<size_t>::max();
  256. return status_t::OK;
  257. case action_t::EXIT_ERROR:
  258. step = std::numeric_limits<size_t>::max();
  259. return status_t::ERROR;
  260. default:
  261. step = step_(step, block.action, block.idx);
  262. return status_t::OK;
  263. }
  264. }
  265. //! @}
  266. public:
  267. /*!
  268. * \brief
  269. * Checks if the \c needle matches the \c haystack.
  270. *
  271. * \param type The type of matching functionality
  272. * \param haystack The stream in witch we search
  273. * \param needle The stream we search
  274. * \return True on match
  275. */
  276. static bool match (const str_view_t haystack, const str_view_t needle, match_t type) {
  277. switch (type) {
  278. default:
  279. case match_t::NO: return true;
  280. case match_t::STARTS_WITH: return starts_with_(haystack, needle);
  281. case match_t::ENDS_WITH: return ends_with_(haystack, needle);
  282. case match_t::CONTAINS: return contains_(haystack, needle);
  283. case match_t::nSTARTS_WITH: return !starts_with_(haystack, needle);
  284. case match_t::nENDS_WITH: return !ends_with_(haystack, needle);
  285. case match_t::nCONTAINS: return !contains_(haystack, needle);
  286. }
  287. }
  288. /*!
  289. * \brief
  290. * A static functionality to provide access to sequencer's inner matching mechanism.
  291. * Checks the \c buffer against \c handle and calls its action if needed.
  292. *
  293. * \param handle Reference to handle
  294. * \param buffer The buffer to check
  295. * \return True on match, false otherwise
  296. */
  297. static bool check_handle (const handle_t& handle, const str_view_t buffer) {
  298. size_t tmp{};
  299. if (match(buffer, handle.token, handle.match_type)) {
  300. action_ (tmp, handle, buffer);
  301. return true;
  302. }
  303. return false;
  304. }
  305. /*!
  306. * \brief
  307. * Run the script array
  308. *
  309. * The main sequencer functionality. It starts with the first entry of the array.
  310. * - If the entry is \c NOP it executes the action after the timeout.
  311. * \c token and \c handler are discarded.
  312. * - If the entry is \c SEND it uses the first handle block's token to send and executes the action after that.
  313. * \c timeout is discarded.
  314. * - If the entry is \c EXCEPTS it continuously try to receive data using implementation's get until one
  315. * of the handle blocks match.
  316. * On match:
  317. * - Calls the handler if there is one
  318. * - Executes the action
  319. * - Skips the next handle blocks if there is any.
  320. * If there is no match on timeout it return status_t::EXIT_ERROR
  321. * - If the entry is \c DETECT it continuously try to detect data using implementation's contents until one
  322. * of the handle blocks match.
  323. * On match:
  324. * - Calls the handler if there is one
  325. * - Executes the action
  326. * - Skips the next handle blocks if there is any.
  327. * If there is no match on timeout it return status_t::EXIT_ERROR
  328. *
  329. * \tparam Steps The number of steps of the script
  330. * \tparam Nhandles The number of handle blocks in the each script record.
  331. *
  332. * \param script Reference to script to run
  333. * \return The status of entire operation as described above
  334. */
  335. template <size_t Steps, size_t Nhandles>
  336. bool run (const script_t<Steps, Nhandles>& script) {
  337. Data_t buffer[N];
  338. size_t resp_size{};
  339. status_t status{};
  340. clock_t mark = clock_();
  341. for (size_t step =0, p_step =0 ; step < Steps ; ) {
  342. const record_t<Nhandles>& it = script[step];
  343. if (step != p_step) {
  344. p_step = step;
  345. mark = clock_();
  346. }
  347. switch (it.control) {
  348. default:
  349. case control_t::NOP:
  350. if ((clock_() - mark) >= it.timeout)
  351. status = action_ (step, it.block[0]);
  352. break;
  353. case control_t::SEND:
  354. if (put_(it.block[0].token.data(), it.block[0].token.size()) != it.block[0].token.size())
  355. return false;
  356. status = action_ (step, it.block[0]);
  357. break;
  358. case control_t::EXPECT:
  359. resp_size = get_(buffer);
  360. if (resp_size) {
  361. for (auto& block : it.block) {
  362. if (match(
  363. {buffer, resp_size},
  364. block.token,
  365. block.match_type)) {
  366. status = action_ (step, block, {buffer, resp_size});
  367. break;
  368. }
  369. }
  370. }
  371. if (it.timeout && (clock_() - mark) >= it.timeout)
  372. return false;
  373. break;
  374. case control_t::DETECT:
  375. auto data = contents_();
  376. if (data.begin() != data.end()) {
  377. for (auto& block : it.block) {
  378. if (match(
  379. {data.begin(), static_cast<size_t>(data.end() - data.begin())},
  380. block.token,
  381. block.match_type)) {
  382. status = action_ (step, block, {buffer, resp_size});
  383. break;
  384. }
  385. }
  386. }
  387. if (it.timeout && (clock_() - mark) >= it.timeout)
  388. return false;
  389. break;
  390. } // switch (it.control)
  391. }
  392. return (status == status_t::OK);
  393. }
  394. };
  395. /*!
  396. * An "empty" block for convenience.
  397. */
  398. template <typename Impl_t, typename Cont_t, typename Data_t, size_t N>
  399. constexpr typename sequencer<Impl_t, Cont_t, Data_t, N>::handle_t Sequencer_null_block = {
  400. "",
  401. sequencer<Impl_t, Cont_t, Data_t, N>::match_t::NO,
  402. nullptr,
  403. sequencer<Impl_t, Cont_t, Data_t, N>::action_t::NO,
  404. 0
  405. };
  406. }
  407. #endif /* TBX_UTILS_SEQUENCER_H_ */