/*! * \file cli_device.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 #ifndef WIN_TRHEADS #include #include #else #include #include #endif namespace test_cli_device { using namespace utl; // test settings constexpr size_t Size = 128; using data_type = char; // cli_device implementer mock. We simulate a BG95 ATmodem for that purpose template class BG95 : public cli_device, N> { using base_type = cli_device, N>; using Queue = equeue; 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) { if (cmd != nullptr) { for (auto& it : cmd_map) { if (!std::strcmp(it.cmd, cmd)) return it.resp; } } return cmd_map[1].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 Queue RxQ{}; std::atomic lines{}; clock_t t=0; public: // cli_device driver requirements BG95() noexcept : RxQ(Queue::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 << RxQ; ++n; } while (*data++ != base_type::delimiter); lines.fetch_sub(1, std::memory_order_acq_rel); return n; } } while (wait); return 0; } size_t contents(char* data) { char* nullpos = std::copy(RxQ.begin(), RxQ.end(), data); *nullpos =0; return nullpos - data; } size_t put (const char* data, size_t n) { const char* reply = cmd_responce (data); while (*reply) RxQ << *reply++; return n; } clock_t clock() noexcept { return ++t; } void clear_clock() noexcept { t=0; } // extra helper for testing purposes void async (event e) { const char* reply =event_responce (e); while (*reply) RxQ << *reply++; } }; // Behavior flag bool handler_flag = false; void handler (const char* data, size_t n) { (void)*data; (void)n; handler_flag = true; } void clear_flag () { handler_flag = false; } TEST(Tcli_device, traits) { EXPECT_EQ ( std::is_default_constructible>::value, true); EXPECT_EQ ( std::is_nothrow_default_constructible>::value, true); EXPECT_EQ (!std::is_copy_constructible>::value, true); EXPECT_EQ (!std::is_copy_assignable>::value, true); EXPECT_EQ ((std::is_same_v::value_type, data_type>), true); EXPECT_EQ ((std::is_same_v::pointer_type, data_type*>), true); EXPECT_EQ ((std::is_same_v::size_type, size_t>), true); EXPECT_EQ ((std::is_same_v::string_view, std::basic_string_view>), true); } /* * Test inetd in non blocking mode */ TEST(Tcli_device, txrx_inetd) { BG95 modem; char buffer[Size]; size_t s =0; const BG95::inetd_handlers<2> async = {{ {"+QMTSTAT:", BG95::starts_with, handler}, {"+QMTRECV", BG95::contains, handler}, }}; s = modem.transmit("", std::strlen("")); EXPECT_EQ (s, 0UL); s = modem.transmit(""); EXPECT_EQ (s, 0UL); s = modem.transmit(nullptr); EXPECT_EQ (s, 0UL); 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 (std::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 (std::strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "+CSQ: 19,99\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (std::strcmp(buffer, "+CSQ: 19,99\r\n"), 0); modem.inetd(false, &async); // parse "\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (std::strcmp(buffer, "\r\n"), 0); modem.inetd(false, &async); // parse "OK\r\n" EXPECT_NE (modem.receive(buffer), 0UL); EXPECT_EQ (std::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(Tcli_device, run) { BG95 modem; using Control = BG95::control_t; const BG95::script_t<4> script = {{ {Control::NOP, "", BG95::nil, BG95::nil, BG95::go_to<1>, 100000}, {Control::SEND, "ATE0\r\n", BG95::nil, BG95::nil, BG95::next, 0}, {Control::EXPECT, "OK\r\n", BG95::ends_with, BG95::nil, BG95::exit_ok, 100000}, {Control::OR_EXPECT,"ERROR", BG95::contains, BG95::nil, BG95::exit_error, 0} }}; std::mutex m; m.lock(); std::thread th1 ([&](){ do modem.inetd(false); while (!m.try_lock()); m.unlock(); }); EXPECT_EQ (modem.run(script), BG95::exit_ok.value); m.unlock(); // stop and join inetd th1.join(); } TEST(Tcli_device, clear_size) { BG95 modem; char buffer[Size]; modem.clear(); EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (modem.receive(buffer), 0UL); modem.transmit("abcd", 4); modem.inetd(false); modem.inetd(false); EXPECT_NE (modem.size(), 0UL); modem.clear(); EXPECT_EQ (modem.size(), 0UL); } TEST(Tcli_device, command_non_extraction) { BG95 modem; char buffer[Size]; std::mutex m; m.lock(); std::thread th1 ([&](){ do modem.inetd(false); while (!m.try_lock()); m.unlock(); }); auto run_receive = [&](size_t times) -> size_t { size_t s =0; for (size_t i=0 ; i::flush>("AT+CREG?\r\n", "%OK\r\n", 0), false); EXPECT_GT (run_receive(100000), 0UL); // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n EXPECT_EQ (modem.command::flush>("AT+CREG?\r\n", "%%%OK\r\n", 0), true); EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (run_receive(100000), 0UL); // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n EXPECT_EQ (modem.command::flush>("AT+CREG?\r\n", "%", 0), true); EXPECT_GT (run_receive(100000), 0UL); EXPECT_EQ (modem.command::flush>("AT\r\n", "%%", 0), true); // returns: "\r\nOK\r\n" EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (run_receive(100000), 0UL); EXPECT_EQ (modem.command::flush>("AT\r\n", "%%%", 10000), false); // returns: "\r\nOK\r\n" EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (run_receive(100000), 0UL); // returns: "\r\n+CREG: 0,5\r\n\r\nOK\r\n EXPECT_EQ (modem.command("AT+CREG?\r\n", "", 0), true); EXPECT_EQ (modem.command("", "%", 0), true); EXPECT_EQ (modem.command("", "%%", 0), true); EXPECT_EQ (modem.command("", "%", 0), true); EXPECT_EQ (modem.command("", "%", 10000), false); EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (run_receive(100000), 0UL); m.unlock(); // stop and join inetd th1.join(); } TEST(Tcli_device, command_extraction) { BG95 modem; char buffer[Size]; std::mutex m; m.lock(); std::thread th1 ([&](){ do modem.inetd(false); while (!m.try_lock()); m.unlock(); }); auto run_receive = [&](size_t times) -> size_t { size_t s =0; for (size_t i=0 ; i("AT+CREG?\r\n", "", 100000), true); EXPECT_EQ (modem.command("", "%", 100000, substr1), true); EXPECT_EQ (std::strcmp("\r\n", substr1), 0); EXPECT_EQ (modem.command("", "%%", 100000, substr1, substr2), true); EXPECT_EQ (std::strcmp("+CREG: 0,5\r\n", substr1), 0); EXPECT_EQ (std::strcmp("\r\n", substr2), 0); EXPECT_EQ (modem.command("", "%", 100000, substr1), true); EXPECT_EQ (std::strcmp("OK\r\n", substr1), 0); EXPECT_EQ (modem.command("", "%", 10000), false); EXPECT_EQ (modem.size(), 0UL); EXPECT_EQ (run_receive(100000), 0UL); m.unlock(); // stop and join inetd th1.join(); } }