From 6b608041745c3c0082d13bc9af4db33ea260fc01 Mon Sep 17 00:00:00 2001 From: Kyle Schwarz Date: Wed, 6 Aug 2025 17:17:36 -0400 Subject: [PATCH] Device: Add com keepalive --- CMakeLists.txt | 1 + device/device.cpp | 14 ++++++-- include/icsneo/api/periodic.h | 53 +++++++++++++++++++++++++++++ include/icsneo/device/device.h | 6 +++- platform/servd.cpp | 9 ++--- test/unit/periodictest.cpp | 62 ++++++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 include/icsneo/api/periodic.h create mode 100644 test/unit/periodictest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67d9506..dfd9a94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -533,6 +533,7 @@ if(LIBICSNEO_BUILD_UNIT_TESTS) test/unit/ringbuffertest.cpp test/unit/apperrordecodertest.cpp test/unit/windowsstrings.cpp + test/unit/periodictest.cpp ) target_link_libraries(libicsneo-unit-tests gtest gtest_main) diff --git a/device/device.cpp b/device/device.cpp index ac6dd87..65b95b2 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -421,7 +421,8 @@ bool Device::disableLogData() { } bool Device::goOnline() { - if(!enableNetworkCommunication(true)) + static constexpr uint32_t onlineTimeoutMs = 5000; + if(!enableNetworkCommunication(true, onlineTimeoutMs)) return false; auto startTime = std::chrono::system_clock::now(); @@ -450,13 +451,19 @@ bool Device::goOnline() { return false; } + // (re)start the keeponline + keeponline = std::make_unique([this] { return enableNetworkCommunication(true, onlineTimeoutMs); }, std::chrono::milliseconds(onlineTimeoutMs / 4)); + online = true; forEachExtension([](const std::shared_ptr& ext) { ext->onGoOnline(); return true; }); + return true; } bool Device::goOffline() { + keeponline.reset(); + forEachExtension([](const std::shared_ptr& ext) { ext->onGoOffline(); return true; }); if(isDisconnected()) { @@ -3482,13 +3489,14 @@ bool Device::writeMACsecConfig(const MACsecMessage& message, uint16_t binaryInde return writeBinaryFile(raw, binaryIndex); } -bool Device::enableNetworkCommunication(bool enable) { +bool Device::enableNetworkCommunication(bool enable, uint32_t timeout) { bool sendMsg = false; if(!com->driver->enableCommunication(enable, sendMsg)) { return false; } if(sendMsg) { - if(!com->sendCommand(Command::EnableNetworkCommunication, enable)) { + const uint8_t* i = (uint8_t*)&timeout; + if(!com->sendCommand(Command::EnableNetworkCommunication, {enable, 0, 0, 0, i[0], i[1], i[2], i[3]})) { return false; } } diff --git a/include/icsneo/api/periodic.h b/include/icsneo/api/periodic.h new file mode 100644 index 0000000..0cc35ae --- /dev/null +++ b/include/icsneo/api/periodic.h @@ -0,0 +1,53 @@ +#ifndef __PERIODIC_H__ +#define __PERIODIC_H__ + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +namespace icsneo { + +class Periodic { +public: + using Callback = std::function; + Periodic(Callback&& callback, const std::chrono::milliseconds& period) : + thread(&Periodic::loop, this, std::move(callback), period) + {} + ~Periodic() { + { + std::scoped_lock lk(mutex); + stop = true; + } + cv.notify_all(); + thread.join(); + } +private: + void loop(Callback&& callback, const std::chrono::milliseconds& period) { + while (true) { + { + std::unique_lock lk(mutex); + cv.wait_for(lk, period, [&]{ return stop; }); + if(stop) { + break; + } + } + if (!callback()) { + break; + } + } + } + bool stop = false; + std::condition_variable cv; + std::mutex mutex; + std::thread thread; +}; + +} // icsneo + +#endif // __cplusplus + +#endif // __PERIODIC_H__ diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index e0c033c..28fbaf3 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -16,6 +16,7 @@ #include #include "icsneo/api/eventmanager.h" #include "icsneo/api/lifetime.h" +#include "icsneo/api/periodic.h" #include "icsneo/device/neodevice.h" #include "icsneo/device/idevicesettings.h" #include "icsneo/device/nullsettings.h" @@ -1037,7 +1038,10 @@ private: */ std::optional getVSADiskSize(); - bool enableNetworkCommunication(bool enable); + bool enableNetworkCommunication(bool enable, uint32_t timeout = 0); + + // Keeponline (keepalive for online) + std::unique_ptr keeponline; }; } diff --git a/platform/servd.cpp b/platform/servd.cpp index 5a845b9..e24be88 100644 --- a/platform/servd.cpp +++ b/platform/servd.cpp @@ -14,13 +14,14 @@ bool Servd::Enabled() { return enabled ? enabled[0] == '1' : false; } -std::vector split(const std::string_view& str, char delim = ' ') -{ +std::vector split(const std::string_view& str, char delim = ' ') { + if(str.empty()) + return {}; std::vector ret; size_t tail = 0; size_t head = 0; - while (head < str.size()) { - if (str[head] == delim) { + while(head < str.size()) { + if(str[head] == delim) { ret.emplace_back(&str[tail], head - tail); tail = head + 1; } diff --git a/test/unit/periodictest.cpp b/test/unit/periodictest.cpp new file mode 100644 index 0000000..6bcd332 --- /dev/null +++ b/test/unit/periodictest.cpp @@ -0,0 +1,62 @@ +#include "icsneo/api/periodic.h" +#include "gtest/gtest.h" + +#include + +using namespace icsneo; + +// no wait, make sure stop works +TEST(PeriodicTest, StartStop) +{ + const auto start = std::chrono::steady_clock::now(); + Periodic p([] { return true; }, std::chrono::milliseconds(1000)); + const auto delta = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + EXPECT_LT(delta.count(), 100); // hopefully enough +} + +// time single cycle +TEST(PeriodicTest, OneCycle) +{ + std::condition_variable cv; + std::mutex mutex; + uint8_t cycles = 0; + const auto start = std::chrono::steady_clock::now(); + { + Periodic p([&] { + { + std::scoped_lock lk(mutex); + ++cycles; + } + cv.notify_one(); + return true; + }, std::chrono::seconds(1)); + std::unique_lock lk(mutex); + cv.wait_for(lk, std::chrono::seconds(2), [&]{ return cycles > 0; }); + } + const auto delta = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + EXPECT_EQ(delta.count(), 1); + EXPECT_EQ(cycles, 1); +} + +TEST(PeriodicTest, TenCycles) +{ + std::condition_variable cv; + std::mutex mutex; + uint8_t cycles = 0; + const auto start = std::chrono::steady_clock::now(); + { + Periodic p([&] { + { + std::scoped_lock lk(mutex); + ++cycles; + } + cv.notify_one(); + return true; + }, std::chrono::milliseconds(100)); + std::unique_lock lk(mutex); + cv.wait_for(lk, std::chrono::seconds(2), [&]{ return cycles >= 10; }); + } + const auto delta = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + EXPECT_EQ(delta.count(), 1); + EXPECT_EQ(cycles, 10); +}