Micro template library A library for building device drivers
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

493 lignes
22 KiB

  1. /*!
  2. * \file dev/sequencer.h
  3. * \brief
  4. * A script based automation tool for send/receive communications
  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 utl_dev_dequencer_h__
  31. #define utl_dev_dequencer_h__
  32. #include <utl/core/impl.h>
  33. #include <utl/container/range.h>
  34. #include <ctime>
  35. #include <array>
  36. #include <string_view>
  37. #include <type_traits>
  38. #include <utility>
  39. #include <tuple>
  40. namespace utl {
  41. /*!
  42. * \class sequencer
  43. * \brief
  44. * A CRTP base class to provide the sequencer functionality.
  45. *
  46. * Sequencer is a script engine with receive/transmit functionalities based on predicates. It has:
  47. * - A program counter like variable named \c step.
  48. * - \c step actions like NEXT, GOTO exit with status etc...
  49. * - Input data match predicates to trigger those actions.
  50. * - Input data handlers to trigger external functionality on predicate match
  51. * - Output data handlers to "edit" data before transmiting them
  52. * - A small predicate set provided to the user. (starts_with, ends_with, contains).
  53. *
  54. * Sequencer can automate communication with a terminal-like device such as AT-command modems, can
  55. * be used to implement communication protocols, or even small http servers.
  56. *
  57. * It can operate based on a script array and handle the outgoing commands and incoming responses.
  58. * The user can create matching rules on received data and hook handlers and actions on them.
  59. *
  60. * The derived class (implementation) has to provide:
  61. * 1) size_t get(Data_t* data);
  62. * This function return 0 or a number of Data_t items. The data points to buffer for the input data.
  63. *
  64. * 3) size_t contents_ (Data_t* data);
  65. * This function return 0 or a number of Data_t items without removing them from the implementer's container
  66. * The data points to buffer for the input data.
  67. *
  68. * 2) size_t put(const Data_t* data, size_t n);
  69. * This function sends to implementation the data pointed by \c data witch have size \c n.
  70. *
  71. * 4) clock_t clock();
  72. * This function return a number to be used as time. The units of this function may be arbitrary but they
  73. * match the units in \c record_t::timeout field.
  74. *
  75. * \tparam Impl_t The type of derived class
  76. * \tparam Data_t The char-like stream item type. Usually \c char
  77. * \tparam N The size of the sequence buffer to temporary store each line from get().
  78. *
  79. * \note
  80. * We need access to derived class container to sneaky get a range of the data beside
  81. * the normal data flow, in order to implement the \see control_t::DETECT operation.
  82. */
  83. template <typename Impl_t, typename Data_t, size_t N>
  84. class sequencer {
  85. _CRTP_IMPL(Impl_t);
  86. //! \name Public types
  87. //! @{
  88. public:
  89. using value_type = Data_t;
  90. using pointer_type = Data_t*;
  91. using size_type = size_t;
  92. using string_view = std::basic_string_view<Data_t>;
  93. /*!
  94. * The sequencer engine status. A variable of this type is returned by
  95. * \see action_().
  96. */
  97. enum class seq_status_t {
  98. CONTINUE, //!< Means we keep looping
  99. EXIT //!< Means, we exit with status the one indicated by \c action_t of the \c record_t
  100. };
  101. //! \enum control_t
  102. //! \brief The control type of the script entry.
  103. enum class control_t {
  104. NOP, //!< No command, dont send or expect anything, used for delays
  105. SEND, //!< Send data to implementation through put()
  106. EXPECT, //!< Expects data from implementation via get()
  107. OR_EXPECT, //!< Expects data from implementation via get() in conjunction with previous EXPECT
  108. DETECT, //!< Detects data into rx buffer without receiving them via contents()
  109. OR_DETECT //!< Detects data into rx buffer without receiving them via contents() in conjunction with
  110. //!< previous DETECT
  111. //! \note
  112. //! The \c DETECT extra incoming channel serve the purpose of sneak into receive
  113. //! buffer and check for data without getting them. This is useful when the receive driver
  114. //! is buffered with a delimiter and we seek for data that don't follow the delimiter pattern.
  115. //!
  116. //! For example:
  117. //! A modem sends responses with '\n' termination but for some "special" command it opens a cursor
  118. //! lets say ">$ " without '\n' at the end.
  119. };
  120. //! \enum action_t
  121. //! \brief
  122. //! Possible response actions for the sequencer. This is the
  123. //! equivalent of changing the program counter of the sequencer
  124. //! and is composed by a type and a value.
  125. //!
  126. struct action_t {
  127. enum {
  128. NO =0, //!< Do not change sequencer's step
  129. NEXT, //!< Go to next sequencer step. In case of EXPECT/DETECT block of records
  130. //!< skip the entire block of EXPECT[, OR_EXPECT[, OR_EXPECT ...]] and go
  131. //!< to the next (non OR_*) control record.
  132. GOTO, //!< Manually sets the step counter to the number of the \c step member.
  133. EXIT, //!< Instruct for an exit returning the action.value as status
  134. } type;
  135. size_t value; //!< Used by \c GOTO to indicate the next sequencer's step.
  136. };
  137. //! A no_action action_t
  138. static constexpr action_t no_action = {action_t::NO, 0};
  139. //! A next action_t
  140. static constexpr action_t next = {action_t::NEXT, 0};
  141. //! A goto action_t template
  142. template <size_t GOTO>
  143. static constexpr action_t go_to = {action_t::GOTO, static_cast<size_t>(GOTO)};
  144. //! An exit ok action_t
  145. static constexpr action_t exit_ok = {action_t::EXIT, 0};
  146. //! An exit error action_t
  147. static constexpr action_t exit_error = {action_t::EXIT, static_cast<size_t>(-1)};
  148. //! A generic exit action_t template
  149. template <size_t Status>
  150. static constexpr action_t exit = {action_t::EXIT, static_cast<size_t>(Status)};
  151. /*!
  152. * Match binary predicate function pointer type.
  153. * Expects two string views and return a boolean.
  154. * It is used by EXPECT/DETECT blocks to trigger their {handler, action} pair.
  155. */
  156. using match_ft = bool (*) (const string_view haystack, const string_view needle);
  157. /*!
  158. * Send/Receive handler function pointer type.
  159. * Expects a pointer to buffer and a size and returns status.
  160. * It is used on predicate match on EXPECT/DETECT blocks, or as data wrapper on SEND blocks.
  161. */
  162. using handler_ft = void (*) (const Data_t*, size_t);
  163. /*!
  164. * \struct record_t
  165. * \brief
  166. * Describes the sequencer's script record entry (line).
  167. */
  168. struct record_t {
  169. control_t control; //!< The control type of the entry
  170. string_view token; //!< String view to token data. [MUST BE null terminated].
  171. //!< This is passed as 2nd argument to match predicate on EXPECT/DETECT, or as
  172. //! {data, size} pair to SEND handler and put_().
  173. //!< If unused set it to ""
  174. match_ft match; //!< Match predicate to used in EXPECT/DETECT blocks
  175. //!< If unused set it to nullptr
  176. handler_ft handler; //!< The handler to called if the match is successful, or before put_()
  177. //!< If unused set it to nullptr
  178. action_t action; //!< Indicates the step manipulation if the match is successful or after NOP and put_()
  179. clock_t timeout; //!< Timeout in CPU time
  180. };
  181. /*!
  182. * \struct script_t
  183. * \brief
  184. * Describes the sequencer's script.
  185. *
  186. * The user can create arrays as the example bellow to act as a script.
  187. * \code
  188. * Seq s;
  189. * const Seq::script_t<4> script = {{
  190. * {Seq::control_t::NOP, "", Seq::nil, Seq::nil, {Seq::action_t::GOTO, 1}, 1000},
  191. *
  192. * {Seq::control_t::SEND, "ATE0\r\n", Seq::nil, Seq::nil, {Seq::action_t::NEXT, 0}, 0},
  193. * {Seq::control_t::EXPECT, "OK\r\n", Seq::ends_with, Seq::nil, {Seq::action_t::EXIT_OK, 0}, 1000},
  194. * {Seq::control_t::OR_EXPECT, "ERROR", Seq::contains, Seq::nil, {Seq::action_t::EXIT_ERROR, 0}, 0}
  195. * }};
  196. * s.run(script);
  197. * \endcode
  198. */
  199. template <size_t Nrecords>
  200. using script_t = std::array<record_t, Nrecords>;
  201. /*!
  202. * \brief
  203. * Check if the \c stream1 is equal to \c stream2
  204. * \param stream1 The stream in witch we search [The input buffer]
  205. * \param stream2 What we search [The record's token]
  206. * \return True on success, false otherwise
  207. */
  208. static constexpr auto equals = [](const string_view stream1, const string_view stream2) noexcept -> bool {
  209. return (stream1 == stream2);
  210. };
  211. /*!
  212. * \brief
  213. * Check if the \c stream starts with the \c prefix
  214. * \param stream The stream in witch we search [The input buffer]
  215. * \param prefix What we search [The record's token]
  216. * \return True on success, false otherwise
  217. */
  218. static constexpr auto starts_with = [](const string_view stream, const string_view prefix) noexcept -> bool {
  219. return (stream.rfind(prefix, 0) != string_view::npos);
  220. };
  221. /*!
  222. * \brief
  223. * Check if the \c stream ends with the \c postfix
  224. * \param stream The stream in witch we search [The input buffer]
  225. * \param postfix What we search [The record's token]
  226. * \return True on success, false otherwise
  227. */
  228. static constexpr auto ends_with = [](const string_view stream, const string_view postfix) -> bool {
  229. if (stream.size() < postfix.size())
  230. return false;
  231. return (
  232. stream.compare(
  233. stream.size() - postfix.size(),
  234. postfix.size(),
  235. postfix) == 0
  236. );
  237. };
  238. /*!
  239. * \brief
  240. * Check if the \c haystack contains the \c needle
  241. * \param haystack The stream in witch we search [The input buffer]
  242. * \param needle What we search [The record's token]
  243. * \return True on success, false otherwise
  244. */
  245. static constexpr auto contains = [](const string_view haystack, const string_view needle) noexcept -> bool {
  246. return (haystack.find(needle) != string_view::npos);
  247. };
  248. //! Always false predicate
  249. static constexpr auto always_true = [](const string_view s1, const string_view s2) noexcept -> bool {
  250. (void)s1; (void)s2;
  251. return true;
  252. };
  253. //! Always false predicate
  254. static constexpr auto always_false = [](const string_view s1, const string_view s2) noexcept -> bool {
  255. (void)s1; (void)s2;
  256. return false;
  257. };
  258. //! Empty predicate or handler
  259. static constexpr auto nil = nullptr;
  260. //! @}
  261. //! \name Object lifetime
  262. //!@{
  263. protected:
  264. ~sequencer () = default; //!< \brief Allow destructor from derived only
  265. constexpr sequencer () noexcept = default; //!< \brief A default constructor from derived only
  266. sequencer(const sequencer&) = delete; //!< No copies
  267. sequencer& operator= (const sequencer&) = delete; //!< No copy assignments
  268. //!@}
  269. //! \name Sequencer interface requirements for implementer
  270. //! @{
  271. private:
  272. size_t get_ (Data_t* data) { return impl().get (data); }
  273. size_t contents_ (Data_t* data) { return impl().contents(data); }
  274. size_t put_ (const Data_t* data, size_t n) { return impl().put (data, n); }
  275. clock_t clock_ () noexcept { return impl().clock(); }
  276. //! @}
  277. //! \name Private functionality
  278. //! @{
  279. private:
  280. /*!
  281. * Check if there is a handler and call it
  282. * \param handler The handler to check
  283. * \param buffer String view to buffer to pass to handler
  284. * \return True if handler is called
  285. */
  286. constexpr bool handle_ (handler_ft handler, const string_view buffer = string_view{}) {
  287. if (handler != nullptr) {
  288. handler (buffer.begin(), buffer.size());
  289. return true;
  290. }
  291. return false;
  292. }
  293. /*!
  294. * \brief
  295. * Return the new sequencer's step value and the sequencer's loop status as pair.
  296. *
  297. * \param script Reference to entire script.
  298. * \param step The current step
  299. * \return new step - status pair
  300. */
  301. template <size_t Steps>
  302. constexpr std::pair<size_t, seq_status_t> action_ (const script_t<Steps>& script, size_t step) {
  303. control_t skip_while{};
  304. size_t s;
  305. switch (script[step].action.type) {
  306. default:
  307. case action_t::NO: return std::make_pair(step, seq_status_t::CONTINUE);
  308. case action_t::NEXT:
  309. switch (script[step].control) {
  310. case control_t::NOP: return std::make_pair(++step, seq_status_t::CONTINUE);
  311. case control_t::SEND: return std::make_pair(++step, seq_status_t::CONTINUE);
  312. case control_t::EXPECT:
  313. case control_t::OR_EXPECT: skip_while = control_t::OR_EXPECT; break;
  314. case control_t::DETECT:
  315. case control_t::OR_DETECT: skip_while = control_t::OR_DETECT; break;
  316. }
  317. s = step;
  318. while (script[++s].control == skip_while)
  319. ;
  320. return std::make_pair(s, seq_status_t::CONTINUE);
  321. case action_t::GOTO: return std::make_pair(script[step].action.value, seq_status_t::CONTINUE);
  322. case action_t::EXIT: return std::make_pair(script[step].action.value, seq_status_t::EXIT);
  323. }
  324. }
  325. //! @}
  326. public:
  327. //! \return The buffer size of the sequencer
  328. constexpr size_t size() const noexcept { return N; }
  329. /*!
  330. * \brief
  331. * A static functionality to provide access to sequencer's inner matching mechanism.
  332. * Checks the \c buffer against \c handle and calls its action if needed.
  333. *
  334. * \param buffer The buffer to check (1st parameter to match)
  335. * \param token String view to check against buffer (2nd parameter to match)
  336. * \param handler Function pointer to match predicate to use
  337. * \param handle Reference to handle to call on match
  338. *
  339. * \return True on match, false otherwise
  340. */
  341. constexpr bool check_handle (const string_view buffer, const string_view token, match_ft match, handler_ft handle) {
  342. if (match != nullptr && match(buffer, token))
  343. return handle_ (handle, buffer);
  344. return false;
  345. }
  346. /*!
  347. * \brief
  348. * Run the script array
  349. *
  350. * The main sequencer functionality. It starts with the first entry of the array.
  351. *
  352. * - If the record is \c NOP it executes the action after the timeout.
  353. * \c NOP uses {\c action_t, \c timeout}.
  354. * - If the record is \c SEND passes the token to handler (if any), then to put_() and executes the action after that.
  355. * \c SEND uses {\c token, \c handler, \c action_t}
  356. * - If the record is \c EXCEPT it continuously try to receive data using \see get_()
  357. * * If no data until timeout, exit with failure
  358. * * On data reception for this record AND for each OR_EXPECT that follows, calls the match predicate
  359. * by passing the received data and token.
  360. * On predicate match
  361. * - Calls the handler if there is one
  362. * - Executes the action. No farther EXPECT, OR_EXPECT, ... checks are made.
  363. * - If the record is \c DETECT it continuously try to receive data using \see contents_()
  364. * * If no data until timeout, exit with failure
  365. * * On data reception for this record AND for each OR_DETECT that follows, calls the match predicate
  366. * by passing the received data and token.
  367. * On predicate match
  368. * - Calls the handler if there is one
  369. * - Executes the action. No farther DETECT, OR_DETECT, ... checks are made.
  370. *
  371. * \tparam Steps The number of records of the script
  372. *
  373. * \param script Reference to script to run
  374. * \return The status of entire operation as described above
  375. * \arg 0 Success
  376. * \arg (size_t)-1 Failure
  377. * \arg other Arbitrary return status
  378. */
  379. template <size_t Steps>
  380. size_t run (const script_t<Steps>& script) {
  381. Data_t buffer[N];
  382. size_t resp_size;
  383. size_t step =0, p_step =0;
  384. clock_t mark = clock_();
  385. seq_status_t status{seq_status_t::CONTINUE}; do {
  386. if (step >= Steps)
  387. return exit_error.value;
  388. const record_t& record = script[step]; // get reference ot current line
  389. if (step != p_step) { // renew time marker in each step
  390. p_step = step;
  391. mark = clock_();
  392. }
  393. switch (record.control) {
  394. default:
  395. case control_t::NOP:
  396. if ((clock_() - mark) >= record.timeout)
  397. std::tie(step, status) = action_ (script, step);
  398. break;
  399. case control_t::SEND:
  400. if (record.handler != nullptr)
  401. record.handler(record.token.data(), record.token.size());
  402. if (put_(record.token.data(), record.token.size()) != record.token.size())
  403. return exit_error.value;
  404. std::tie(step, status) = action_ (script, step);
  405. break;
  406. case control_t::EXPECT:
  407. case control_t::OR_EXPECT:
  408. resp_size = get_(buffer);
  409. if (resp_size) {
  410. size_t s = step ; do{
  411. if (script[s].match != nullptr && script[s].match({buffer, resp_size}, script[s].token)) {
  412. handle_ (script[s].handler, {buffer, resp_size});
  413. std::tie(step, status) = action_ (script, s);
  414. break;
  415. }
  416. } while ((++s < Steps) && (script[s].control == control_t::OR_EXPECT));
  417. }
  418. if (record.timeout && (clock_() - mark) >= record.timeout)
  419. return exit_error.value;
  420. break;
  421. case control_t::DETECT:
  422. case control_t::OR_DETECT:
  423. resp_size = contents_(buffer);
  424. if (resp_size) {
  425. size_t s = step ; do {
  426. if (script[s].match != nullptr && script[s].match({buffer, resp_size}, script[s].token)) {
  427. handle_ (script[s].handler, {buffer, resp_size});
  428. std::tie(step, status) = action_ (script, s);
  429. break;
  430. }
  431. } while ((++s < Steps) && (script[s].control == control_t::OR_DETECT));
  432. }
  433. if (record.timeout && (clock_() - mark) >= record.timeout)
  434. return exit_error.value;
  435. break;
  436. } // switch (record.control)
  437. } while ( status == seq_status_t::CONTINUE);
  438. return step; // step here is set by action_ as the return status
  439. }
  440. };
  441. } //namespace utl;
  442. #endif /* utl_dev_dequencer_h__ */