/*! * \file sequencer.cpp * * \copyright Copyright (C) 2020 Christos Choutouridis * *
License
* The MIT License (MIT) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *
* */ #include #include #include //#include //#include #include #include #ifndef WIN_TRHEADS #include #include #else #include #include #endif namespace test_bg95_base { using namespace tbx; using Q = equeue; // BG95 implementer mock class BG95 : public BG95_base { using base_type = BG95_base; public: enum class event { MQTT_DISCONNECT, MQTT_RXDATA }; // simulated modem operation private: struct cmd_pair { const char *cmd; const char *resp; }; struct event_pair { event e; const char* resp; }; std::array cmd_map = {{ {"ERROR", "\r\nERROR\r\n"}, {"ATE0\r\n", "\r\nATE0\r\nOK\r\n"}, {"AT\r\n", "\r\nOK\r\n"}, {"AT+QCFG=\"nwscanseq\"\r\n", "\r\n+QCFG: \"nwscanseq\",020301\r\n"}, {"AT+QCFG=\"nwscanseq\",010302\r\n", "\r\nOK\r\n"}, {"AT+CREG?\r\n", "\r\n+CREG: 0,5\r\n\r\nOK\r\n"}, {"AT+CSQ\r\n", "\r\n+CSQ: 19,99\r\n\r\nOK\r\n"}, {"AT+QNWINFO\r\n", "\r\n+QNWINFO: \"EDGE\",\"20201\",\"GSM 1800\",865\r\n\r\nOK\r\n"}, // Files {"AT+QFLST\r\n", "\r\n+QFLST: \"cacert.pem\",1220\r\n+QFLST: \"security/\",2\r\nOK\r\n"}, // MQTT config {"AT+QSSLCFG=\"ignorelocaltime\",2,1\r\n", "\r\nOK\r\n"}, {"AT+QSSLCFG=\"seclevel\",2,1\r\n", "\r\nOK\r\n"}, {"AT+QSSLCFG=\"sslversion\",2,4\r\n", "\r\nOK\r\n"}, {"AT+QSSLCFG=\"ciphersuite\",2\r\n", "\r\n+QSSLCFG: \"ciphersuite\",2,0XFFFF\r\n\r\nOK\r\n"}, {"AT+QMTCFG=\"ssl\",0,1,2\r\n", "\r\nOK\r\n"}, {"AT+QMTCFG=\"keepalive\",0,3600\r\n", "\r\nOK\r\n"}, // MQTT {"AT+QMTOPEN=0,\"server.com\",8883\r\n", "\r\nOK\r\n\r\n+QMTOPEN: 0,0\r\n"}, {"AT+QMTCONN=0,\"myID\",\"user\",\"pass\"\r\n", "\r\nOK\r\n\r\n+QMTCONN: 0,0,0\r\n"}, {"AT+QMTSUB=0,1,\"/path/topic1\",2\r\n", "\r\nOK\r\n\r\n+QMTSUB: 0,1,0,2\r\n"}, {"AT+QMTPUB=0,0,0,0,\"/path/topic2\",9\r\n", "\r\n> \r\nOK\r\n\r\n+QMTPUB: 0,0,0\r\n"}, }}; std::array event_map {{ {event::MQTT_DISCONNECT, "\r\n+QMTSTAT: 0,1\r\n"}, {event::MQTT_RXDATA, "\r\n+QMTRECV: 0,1,\"/path/topic1\",\"BR: hello to all of my subscribers\""} }}; const char* cmd_responce (const char* cmd) { for (auto& it : cmd_map) { if (!strcmp(it.cmd, cmd)) return it.resp; } return cmd_map[0].resp; } const char* event_responce (const event e) { for (auto& it : event_map) { if (e == it.e) return it.resp; } return nullptr; // non reachable } // data Q rx_q{}; std::atomic lines{}; public: using range_t = typename Q::range_t; public: // BG95_base driver requirements BG95() : rx_q(Q::data_match::MATCH_PUSH, base_type::delimiter, [&](){ lines.fetch_add(1, std::memory_order_acq_rel); }), lines(0) { } size_t get(char* data, bool wait =false) { do { if (lines.load(std::memory_order_acquire)) { size_t n =0; do{ *data << rx_q; ++n; } while (*data++ != base_type::delimiter); lines.fetch_sub(1, std::memory_order_acq_rel); return n; } } while (wait); return 0; } size_t put (const char* data, size_t n) { std::cerr << " "; const char* reply = cmd_responce (data); while (*reply) rx_q << *reply++; return n; } const range_t contents() const { return range_t {rx_q.begin(), rx_q.end()}; } clock_t clock() { static clock_t t=0; return ++t; } // extra helper for testing purposes void async (event e) { const char* reply =event_responce (e); while (*reply) rx_q << *reply++; } }; // Behavior flag bool handler_flag = false; void handler (const char* data, size_t n) { (void)*data; (void)n; // std::cout << "* handler called\n"; handler_flag = true; } void clear_flag () { handler_flag = false; } /* * Test inetd in non blocking mode */ TEST(TBG95_base, inetd_non_blocking) { BG95 modem; char buffer[256]; const BG95::async_handlers<2> async = {{ {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, }}; clear_flag(); modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); modem.async(BG95::event::MQTT_DISCONNECT); modem.inetd(false, &async); // parse "\r\n" EXPECT_EQ (handler_flag, false); modem.inetd(false, &async); // parse "+QMT*\r\n" and dispatch to handler() EXPECT_EQ (handler_flag, true); clear_flag(); // nothing to parse modem.inetd(false, &async); modem.inetd(false, &async); modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); EXPECT_NE (modem.receive(buffer), 0UL); // "\r\n" in buffer EXPECT_EQ (strcmp(buffer, "\r\n"), 0); clear_flag(); modem.inetd(false, &async); EXPECT_EQ (handler_flag, false); modem.transmit("AT+CSQ\r\n", 8); EXPECT_EQ (modem.receive(buffer), 0UL); modem.inetd(false, &async); // parse "\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "+CSQ: 19,99\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (strcmp(buffer, "+CSQ: 19,99\r\n"), 0); modem.inetd(false, &async); // parse "\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "OK\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (strcmp(buffer, "OK\r\n"), 0); modem.inetd(false, &async); // nothing to parse modem.inetd(false, &async); modem.inetd(false, &async); EXPECT_EQ (modem.receive(buffer), 0UL); } TEST(TBG95_base, run) { BG95 modem; const BG95::async_handlers<2> async = {{ {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, }}; const BG95::script_t<5> script = {{ /* 0 */{BG95::control_t::NOP, {"", BG95::match_t::NO, nullptr, BG95::action_t::GOTO, 1}, 1000}, /* 1 */{BG95::control_t::SEND, {"ATE0\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, /* 2 */{BG95::control_t::EXPECT, {{ {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::NEXT, 0}, {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, 1000 }, /* 3 */{BG95::control_t::SEND, {"AT+CSQ\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, /* 4 */{BG95::control_t::EXPECT, {{ {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::EXIT_OK, 0}, {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, 1000 }, }}; std::atomic lock(true); std::thread th1 ([&](){ do modem.inetd(false, &async); while (lock.load(std::memory_order_acquire)); }); EXPECT_EQ (modem.run(script), true); lock.store(false, std::memory_order_acq_rel); th1.join(); } TEST(TBG95_base, command) { BG95 modem; std::atomic lock(true); std::thread th1 ([&](){ do modem.inetd(false); while (lock.load(std::memory_order_acquire)); }); EXPECT_EQ (modem.registered(), true); lock.store(false, std::memory_order_acq_rel); th1.join(); } }