changes in task handling
This commit is contained in:
@@ -1,108 +1,123 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace snoop
|
||||
{
|
||||
namespace device_sec
|
||||
{
|
||||
// --- helpers ---
|
||||
static std::string Trim(const std::string &s)
|
||||
{
|
||||
auto b = s.find_first_not_of(" \t\r\n");
|
||||
auto e = s.find_last_not_of(" \t\r\n");
|
||||
if (b == std::string::npos)
|
||||
return "";
|
||||
return s.substr(b, e - b + 1);
|
||||
}
|
||||
|
||||
static std::string Exec(const std::string &cmd)
|
||||
{
|
||||
std::array<char, 4096> buf{};
|
||||
std::string out;
|
||||
FILE *pipe = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!pipe)
|
||||
throw std::runtime_error("popen failed: " + cmd);
|
||||
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr)
|
||||
out.append(buf.data());
|
||||
int rc = pclose(pipe);
|
||||
int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc;
|
||||
if (exitCode != 0)
|
||||
spdlog::warn("Command '{}' exited with code {}", cmd, exitCode);
|
||||
return out;
|
||||
}
|
||||
|
||||
// dumps the client key from keyring to a temp file and returns its path
|
||||
static std::filesystem::path ExtractClientKeyFromKernelKeyring()
|
||||
{
|
||||
std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1"));
|
||||
if (id.empty())
|
||||
throw std::runtime_error("iot-client-key not found in keyring");
|
||||
// Create a secure temp file
|
||||
char tmpl[] = "/run/iot/iot-keyXXXXXX";
|
||||
int fd = mkstemp(tmpl);
|
||||
if (fd < 0)
|
||||
throw std::runtime_error("mkstemp failed for client key");
|
||||
close(fd);
|
||||
std::filesystem::path p(tmpl);
|
||||
|
||||
// Pipe the key payload into the temp file
|
||||
std::string cmd = "keyctl pipe " + id + " > " + p.string();
|
||||
Exec(cmd);
|
||||
|
||||
// quick sanity
|
||||
if (std::filesystem::file_size(p) == 0)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(p, ec);
|
||||
throw std::runtime_error("keyctl pipe produced empty client key");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
struct TempFile
|
||||
{
|
||||
std::filesystem::path path;
|
||||
int fd{-1};
|
||||
explicit TempFile(const std::filesystem::path &dir, const char *pattern = "iot-keyXXXXXX")
|
||||
{
|
||||
std::string tmpl = (dir / pattern).string();
|
||||
std::vector<char> name(tmpl.begin(), tmpl.end());
|
||||
name.push_back('\0');
|
||||
fd = mkstemp(name.data());
|
||||
if (fd < 0)
|
||||
throw std::runtime_error("mkstemp failed");
|
||||
fchmod(fd, S_IRUSR | S_IWUSR);
|
||||
path = name.data();
|
||||
}
|
||||
void write_all(const void *data, size_t n)
|
||||
{
|
||||
const uint8_t *p = static_cast<const uint8_t *>(data);
|
||||
size_t off = 0;
|
||||
while (off < n)
|
||||
{
|
||||
ssize_t w = ::write(fd, p + off, n - off);
|
||||
if (w <= 0)
|
||||
throw std::runtime_error("write failed");
|
||||
off += (size_t)w;
|
||||
}
|
||||
fsync(fd);
|
||||
}
|
||||
~TempFile()
|
||||
{
|
||||
if (fd >= 0)
|
||||
::close(fd);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path, ec);
|
||||
}
|
||||
};
|
||||
}
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace snoop
|
||||
{
|
||||
namespace device_sec
|
||||
{
|
||||
// --- helpers ---
|
||||
static std::string Trim(const std::string &s)
|
||||
{
|
||||
auto b = s.find_first_not_of(" \t\r\n");
|
||||
auto e = s.find_last_not_of(" \t\r\n");
|
||||
if (b == std::string::npos)
|
||||
return "";
|
||||
return s.substr(b, e - b + 1);
|
||||
}
|
||||
|
||||
static std::string Exec(const std::string &cmd)
|
||||
{
|
||||
std::array<char, 4096> buf{};
|
||||
std::string out;
|
||||
FILE *pipe = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!pipe)
|
||||
throw std::runtime_error("popen failed: " + cmd);
|
||||
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr)
|
||||
out.append(buf.data());
|
||||
int rc = pclose(pipe);
|
||||
int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc;
|
||||
if (exitCode != 0)
|
||||
spdlog::warn("Command '{}' exited with code {}", cmd, exitCode);
|
||||
return out;
|
||||
}
|
||||
|
||||
// dumps the client key from keyring to a temp file and returns its path
|
||||
static std::filesystem::path ExtractClientKeyFromKernelKeyring()
|
||||
{
|
||||
std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1"));
|
||||
if (id.empty())
|
||||
throw std::runtime_error("iot-client-key not found in keyring");
|
||||
// Create a secure temp file
|
||||
char tmpl[] = "/run/iot/iot-keyXXXXXX";
|
||||
int fd = mkstemp(tmpl);
|
||||
if (fd < 0)
|
||||
throw std::runtime_error("mkstemp failed for client key");
|
||||
close(fd);
|
||||
std::filesystem::path p(tmpl);
|
||||
|
||||
// Pipe the key payload into the temp file
|
||||
std::string cmd = "keyctl pipe " + id + " > " + p.string();
|
||||
Exec(cmd);
|
||||
|
||||
// quick sanity
|
||||
if (std::filesystem::file_size(p) == 0)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(p, ec);
|
||||
throw std::runtime_error("keyctl pipe produced empty client key");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
struct TempFile
|
||||
{
|
||||
std::filesystem::path path;
|
||||
int fd{-1};
|
||||
explicit TempFile(const std::filesystem::path &dir, const char *pattern = "iot-keyXXXXXX")
|
||||
{
|
||||
std::string tmpl = (dir / pattern).string();
|
||||
std::vector<char> name(tmpl.begin(), tmpl.end());
|
||||
name.push_back('\0');
|
||||
fd = mkstemp(name.data());
|
||||
if (fd < 0)
|
||||
throw std::runtime_error("mkstemp failed");
|
||||
fchmod(fd, S_IRUSR | S_IWUSR);
|
||||
path = name.data();
|
||||
}
|
||||
void write_all(const void *data, size_t n)
|
||||
{
|
||||
const uint8_t *p = static_cast<const uint8_t *>(data);
|
||||
size_t off = 0;
|
||||
while (off < n)
|
||||
{
|
||||
ssize_t w = ::write(fd, p + off, n - off);
|
||||
if (w <= 0)
|
||||
throw std::runtime_error("write failed");
|
||||
off += (size_t)w;
|
||||
}
|
||||
fsync(fd);
|
||||
}
|
||||
~TempFile()
|
||||
{
|
||||
if (fd >= 0)
|
||||
::close(fd);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path, ec);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<uint8_t> ReadClientKeyPayloadFromKeyring()
|
||||
{
|
||||
// 1) get key id
|
||||
std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1"));
|
||||
if (id.empty())
|
||||
throw std::runtime_error("iot-client-key not found in keyring");
|
||||
|
||||
// 2) capture payload (no redirection to file)
|
||||
std::string bytes = Exec("keyctl pipe " + id);
|
||||
if (bytes.empty())
|
||||
throw std::runtime_error("keyctl pipe returned empty payload");
|
||||
|
||||
return std::vector<uint8_t>(bytes.begin(), bytes.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "WhipClient.h"
|
||||
#include "ConfigService.h"
|
||||
#include "Security/TlsKeyUtil.h"
|
||||
|
||||
namespace snoop {
|
||||
|
||||
@@ -59,8 +60,8 @@ public:
|
||||
std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
||||
|
||||
// extract client key via keyctl
|
||||
auto tmpKey = ExtractClientKeyTemp();
|
||||
if (!tmpKey) {
|
||||
auto tmpKey = snoop::device_sec::ExtractClientKeyFromKernelKeyring();
|
||||
if (!tmpKey.string().empty()) {
|
||||
spdlog::error("Cannot extract client key for WHIP (keyctl user iot-client-key)");
|
||||
return false;
|
||||
}
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
.whipUrl = whipUrl,
|
||||
.caPath = ca.string(),
|
||||
.crtPath = crt.string(),
|
||||
.keyPath = tmpKey->string(),
|
||||
.keyPath = tmpKey,
|
||||
.sampleRate= sampleRate,
|
||||
.channels = channels
|
||||
};
|
||||
@@ -77,11 +78,11 @@ public:
|
||||
try {
|
||||
m_whip->Start();
|
||||
spdlog::info("WHIP started");
|
||||
m_tmpKeyPath = *tmpKey;
|
||||
m_tmpKeyPath = tmpKey;
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("WHIP start failed: {}", e.what());
|
||||
std::error_code ec; std::filesystem::remove(*tmpKey, ec);
|
||||
std::error_code ec; std::filesystem::remove(tmpKey, ec);
|
||||
m_whip.reset();
|
||||
return false;
|
||||
}
|
||||
@@ -100,36 +101,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static std::optional<std::filesystem::path> ExtractClientKeyTemp() {
|
||||
auto exec = [](const std::string& cmd) {
|
||||
std::array<char, 4096> buf{};
|
||||
std::string out;
|
||||
FILE* pipe = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!pipe) return std::string{};
|
||||
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) out.append(buf.data());
|
||||
pclose(pipe);
|
||||
return out;
|
||||
};
|
||||
auto trim = [](std::string s){
|
||||
auto b=s.find_first_not_of(" \t\r\n"), e=s.find_last_not_of(" \t\r\n");
|
||||
return (b==std::string::npos) ? std::string{} : s.substr(b, e-b+1);
|
||||
};
|
||||
|
||||
std::string id = trim(exec("keyctl search @s user iot-client-key | tail -n1"));
|
||||
if (id.empty()) return std::nullopt;
|
||||
|
||||
char tmpl[] = "/run/iot-whip-keyXXXXXX";
|
||||
int fd = mkstemp(tmpl);
|
||||
if (fd < 0) return std::nullopt;
|
||||
close(fd);
|
||||
std::filesystem::path p(tmpl);
|
||||
exec("keyctl pipe " + id + " > " + p.string());
|
||||
if (!std::filesystem::exists(p) || std::filesystem::file_size(p) == 0) {
|
||||
std::error_code ec; std::filesystem::remove(p, ec);
|
||||
return std::nullopt;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace snoop
|
||||
|
||||
@@ -147,6 +147,13 @@ namespace snoop
|
||||
int port = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<httplib::SSLClient> m_uploadClient;
|
||||
std::string m_uploadHost;
|
||||
int m_uploadPort = 0;
|
||||
bool m_uploadHttps = true;
|
||||
std::filesystem::path m_uploadCa;
|
||||
std::filesystem::path m_uploadCrt;
|
||||
|
||||
static Url ParseBase(const std::string &base)
|
||||
{
|
||||
std::regex re(R"(^\s*(https?)://([^/:]+)(?::(\d+))?\s*$)");
|
||||
@@ -163,9 +170,9 @@ namespace snoop
|
||||
}
|
||||
|
||||
std::unique_ptr<httplib::SSLClient> MakeClientMTLS(const Url &u,
|
||||
const std::filesystem::path &ca,
|
||||
const std::filesystem::path &crt,
|
||||
const std::filesystem::path &key)
|
||||
const std::filesystem::path &ca,
|
||||
const std::filesystem::path &crt,
|
||||
const std::filesystem::path &key)
|
||||
{
|
||||
if (u.scheme == "https")
|
||||
{
|
||||
@@ -181,14 +188,63 @@ namespace snoop
|
||||
throw std::runtime_error("HTTPS baseUrl but CPPHTTPLIB_OPENSSL_SUPPORT is not enabled");
|
||||
#endif
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
||||
// cli->set_connection_timeout(10);
|
||||
// cli->set_read_timeout(120);
|
||||
// cli->set_write_timeout(120);
|
||||
// return cli;
|
||||
// }
|
||||
}
|
||||
|
||||
bool EnsureUploadClient()
|
||||
{
|
||||
// Recompute endpoint pieces
|
||||
const auto baseUrl = this->m_configService->GetBaseUrl();
|
||||
Url url = ParseBase(baseUrl);
|
||||
|
||||
// Detect changes requiring re-init
|
||||
bool needReinit =
|
||||
!m_uploadClient ||
|
||||
m_uploadHost != url.host || m_uploadPort != url.port ||
|
||||
(url.scheme == "https") != m_uploadHttps;
|
||||
|
||||
// Setup CA/CRT (from enrollment)
|
||||
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
|
||||
if (!std::filesystem::exists(ca))
|
||||
ca = "/etc/iot/keys/ca_chain.pem";
|
||||
const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
||||
|
||||
if (!needReinit)
|
||||
{
|
||||
// still ok — reuse
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
try
|
||||
{
|
||||
auto payload = snoop::device_sec::ReadClientKeyPayloadFromKeyring();
|
||||
// pick a writable dir (use /tmp to avoid /run root perms surprises)
|
||||
snoop::device_sec::TempFile tf(std::filesystem::temp_directory_path());
|
||||
tf.write_all(payload.data(), payload.size());
|
||||
|
||||
// OpenSSL loads the files now; once this returns, the key is in-memory.
|
||||
auto cli = MakeClientMTLS(url, ca, crt, tf.path);
|
||||
// tf destructor will remove the file at scope end
|
||||
// Swap in
|
||||
m_uploadClient = std::move(cli);
|
||||
m_uploadHost = url.host;
|
||||
m_uploadPort = url.port;
|
||||
m_uploadHttps = (url.scheme == "https");
|
||||
m_uploadCa = ca;
|
||||
m_uploadCrt = crt;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("EnsureUploadClient: failed to set client cert: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
(void)ca;
|
||||
(void)crt;
|
||||
spdlog::error("HTTPS baseUrl but CPPHTTPLIB_OPENSSL_SUPPORT not enabled");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ----------------------------- Existing logic (adjusted) -----------------------------
|
||||
@@ -307,23 +363,13 @@ namespace snoop
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare client key (temp file) for this upload pass
|
||||
std::optional<std::filesystem::path> tmpKey;
|
||||
try
|
||||
{
|
||||
tmpKey = snoop::device_sec::ExtractClientKeyFromKernelKeyring();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("Cannot extract client key for mTLS: {}", e.what());
|
||||
// Wait a bit and retry later
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto client = MakeClientMTLS(url, ca, crt, *tmpKey);
|
||||
if (!EnsureUploadClient())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto &filePath : files)
|
||||
{
|
||||
@@ -341,7 +387,7 @@ namespace snoop
|
||||
|
||||
try
|
||||
{
|
||||
if (SendRecordedFileMTLS(*client, filePath.string(),
|
||||
if (SendRecordedFileMTLS(*m_uploadClient, filePath.string(),
|
||||
std::stoull(startedAt),
|
||||
std::stoull(stoppedAt)))
|
||||
{
|
||||
@@ -356,6 +402,7 @@ namespace snoop
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("Exception during file upload: {}", e.what());
|
||||
m_uploadClient.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,14 +410,6 @@ namespace snoop
|
||||
{
|
||||
spdlog::error("mTLS client setup failed: {}", e.what());
|
||||
}
|
||||
|
||||
// cleanup temp key asap
|
||||
if (tmpKey)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(*tmpKey, ec);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +57,6 @@ namespace snoop
|
||||
std::function<void()> stopRecordingNow; // e.g., writerService->StopRecordingNow()
|
||||
};
|
||||
|
||||
// DeviceControlService(std::shared_ptr<ConfigService> cfg, Handlers handlers)
|
||||
// : m_cfg(std::move(cfg)), m_handlers(std::move(handlers))
|
||||
// {
|
||||
// m_stop = false;
|
||||
// m_thread = std::thread(&DeviceControlService::RunLoop, this);
|
||||
// }
|
||||
|
||||
DeviceControlService(std::shared_ptr<ConfigService> cfg, Handlers handlers, Controls controls)
|
||||
: m_cfg(std::move(cfg)), m_handlers(std::move(handlers)), m_controls(std::move(controls))
|
||||
{
|
||||
@@ -156,6 +149,13 @@ namespace snoop
|
||||
int port = 0; // default if 0
|
||||
};
|
||||
|
||||
std::unique_ptr<httplib::SSLClient> m_taskClient;
|
||||
std::string m_taskHost;
|
||||
int m_taskPort = 0;
|
||||
bool m_taskHttps = true;
|
||||
std::filesystem::path m_taskCa;
|
||||
std::filesystem::path m_taskCrt;
|
||||
|
||||
static Url ParseBase(const std::string &base)
|
||||
{
|
||||
// very small parser: scheme://host[:port]
|
||||
@@ -195,7 +195,6 @@ namespace snoop
|
||||
}
|
||||
|
||||
// dumps the client key from keyring to a temp file and returns its path
|
||||
|
||||
|
||||
// Create HTTPS client configured for mTLS (or HTTP if base is http)
|
||||
std::unique_ptr<httplib::SSLClient> MakeClient(const Url &u,
|
||||
@@ -212,19 +211,24 @@ namespace snoop
|
||||
cli->set_connection_timeout(10);
|
||||
cli->set_read_timeout(60);
|
||||
cli->set_write_timeout(60);
|
||||
|
||||
if (!cli->is_valid())
|
||||
{
|
||||
spdlog::warn("[mTLS debug] SSLClient is not valid right after creation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::debug("[mTLS debug] SSLClient initialized OK for host={} port={}", u.host, u.port);
|
||||
}
|
||||
return cli;
|
||||
#else
|
||||
(void)u;
|
||||
(void)ca;
|
||||
(void)crt;
|
||||
(void)key;
|
||||
throw std::runtime_error("CPPHTTPLIB_OPENSSL_SUPPORT not enabled but https URL provided");
|
||||
#endif
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
||||
// cli->set_connection_timeout(10);
|
||||
// cli->set_read_timeout(60);
|
||||
// cli->set_write_timeout(60);
|
||||
// return cli;
|
||||
// }
|
||||
}
|
||||
|
||||
// simple jittered sleep
|
||||
@@ -354,34 +358,94 @@ namespace snoop
|
||||
m_controls.stopRecordingNow();
|
||||
}
|
||||
|
||||
bool EnsureTaskClient()
|
||||
{
|
||||
// Recompute endpoint pieces
|
||||
const auto baseUrl = this->m_cfg->GetBaseUrl();
|
||||
Url url = ParseBase(baseUrl);
|
||||
|
||||
bool needReinit = !m_taskClient ||
|
||||
m_taskHost != url.host || m_taskPort != url.port ||
|
||||
(url.scheme == "https") != m_taskHttps;
|
||||
|
||||
// Setup CA/CRT (from enrollment)
|
||||
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
|
||||
if (!std::filesystem::exists(ca))
|
||||
ca = "/etc/iot/keys/ca_chain.pem";
|
||||
const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
||||
|
||||
if (!needReinit)
|
||||
{
|
||||
// still ok — reuse
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
try
|
||||
{
|
||||
auto payload = snoop::device_sec::ReadClientKeyPayloadFromKeyring();
|
||||
// pick a writable dir (use /tmp to avoid /run root perms surprises)
|
||||
snoop::device_sec::TempFile tf(std::filesystem::temp_directory_path());
|
||||
tf.write_all(payload.data(), payload.size());
|
||||
|
||||
// OpenSSL loads the files now; once this returns, the key is in-memory.
|
||||
auto cli = MakeClient(url, ca, crt, tf.path);
|
||||
spdlog::info("Creating new client because of change");
|
||||
// tf destructor will remove the file at scope end
|
||||
// Swap in
|
||||
m_taskClient = std::move(cli);
|
||||
m_taskHost = url.host;
|
||||
m_taskPort = url.port;
|
||||
m_taskHttps = (url.scheme == "https");
|
||||
m_taskCa = ca;
|
||||
m_taskCrt = crt;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("EnsureTaskClient: failed to set client cert: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
(void)ca;
|
||||
(void)crt;
|
||||
spdlog::error("HTTPS baseUrl but CPPHTTPLIB_OPENSSL_SUPPORT not enabled");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void RunLoop()
|
||||
{
|
||||
const std::string guid = m_cfg->GetGuid();
|
||||
const auto base = m_cfg->GetBaseUrl();
|
||||
Url url = ParseBase(base);
|
||||
|
||||
// Cert paths from enrollment step
|
||||
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem"; // or ca_chain.pem if you prefer
|
||||
if (!std::filesystem::exists(ca))
|
||||
ca = "/etc/iot/keys/ca_chain.pem";
|
||||
std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
||||
|
||||
std::mt19937 rng{std::random_device{}()};
|
||||
|
||||
while (!m_stop)
|
||||
{
|
||||
// Extract client key from kernel keyring to a temp file each cycle (kept minimal on disk)
|
||||
std::optional<std::filesystem::path> tmpKey;
|
||||
try
|
||||
// (1) Ensure we have a reusable client
|
||||
if (!EnsureTaskClient())
|
||||
{
|
||||
tmpKey = snoop::device_sec::ExtractClientKeyFromKernelKeyring();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("Key extraction failed: {}", e.what());
|
||||
spdlog::info("Sleep after task client");
|
||||
SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
|
||||
continue;
|
||||
}
|
||||
//// FOR SSL DEBUG - remove in prod
|
||||
if (m_taskClient)
|
||||
{
|
||||
long verify_result = m_taskClient->get_openssl_verify_result();
|
||||
if (verify_result == X509_V_OK)
|
||||
{
|
||||
spdlog::info("[mTLS debug] OpenSSL verify: SUCCESS (X509_V_OK)");
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *err_str = X509_verify_cert_error_string(verify_result);
|
||||
spdlog::error("[mTLS debug] OpenSSL verify failed: {} ({})",
|
||||
err_str ? err_str : "Unknown error", verify_result);
|
||||
}
|
||||
}
|
||||
/////////////////////////////////
|
||||
|
||||
// (2) Deep-sleep gate
|
||||
const auto now = NowMs();
|
||||
const auto startMs = m_sleepStartMs.load();
|
||||
const auto untilMs = m_sleepUntilMs.load();
|
||||
@@ -390,78 +454,72 @@ namespace snoop
|
||||
{
|
||||
if (now < startMs)
|
||||
{
|
||||
// Not yet time to sleep: wait until start (no network calls)
|
||||
// not yet sleeping: wait until start, no requests
|
||||
auto waitMs = std::min<long long>(startMs - now, 60'000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
|
||||
continue;
|
||||
}
|
||||
if (now >= startMs && now < untilMs)
|
||||
{
|
||||
// In deep sleep window: do not send any requests; just wait
|
||||
auto waitMs = std::min<long long>(untilMs - now, 60'000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
|
||||
// On first entry, ensure we stopped immediately
|
||||
// sleeping: no requests; ensure we stopped stream/recording on entry
|
||||
if (now - startMs < 1200)
|
||||
{ // within ~1.2s window
|
||||
{
|
||||
if (m_controls.stopStreamNow)
|
||||
m_controls.stopStreamNow();
|
||||
if (m_controls.stopRecordingNow)
|
||||
m_controls.stopRecordingNow();
|
||||
}
|
||||
auto waitMs = std::min<long long>(untilMs - now, 60'000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
|
||||
continue;
|
||||
}
|
||||
if (now >= untilMs)
|
||||
{
|
||||
// Sleep period over; clear flags and resume normal polling
|
||||
// wake up
|
||||
m_sleepStartMs = 0;
|
||||
m_sleepUntilMs = 0;
|
||||
spdlog::info("Deep sleep finished; resuming operation");
|
||||
}
|
||||
try
|
||||
{
|
||||
auto cli = MakeClient(url, ca, crt, *tmpKey);
|
||||
|
||||
// --- GET /tasks/:guid
|
||||
const std::string getPath = "/api/tasks/" + guid;
|
||||
spdlog::info("GET {}", getPath);
|
||||
auto res = cli->Get(getPath.c_str());
|
||||
if (!res)
|
||||
{
|
||||
spdlog::warn("GET {} failed (no response)", getPath);
|
||||
}
|
||||
else if (res->status == 204)
|
||||
{
|
||||
spdlog::debug("No tasks (204).");
|
||||
}
|
||||
else if (res->status >= 200 && res->status < 300)
|
||||
{
|
||||
auto tasks = ParseTasks(res->body);
|
||||
for (const auto &t : tasks)
|
||||
{
|
||||
auto handler = ResolveHandler(t.type);
|
||||
auto [ok, resultJson, err] = handler(t);
|
||||
PostResult(*cli, guid, t.id, ok, resultJson, err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::warn("GET {} -> HTTP {}, body: {}", getPath, res->status, res->body);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("Task loop error: {}", e.what());
|
||||
}
|
||||
|
||||
// cleanup temp key ASAP
|
||||
if (tmpKey)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(*tmpKey, ec);
|
||||
}
|
||||
|
||||
SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
|
||||
}
|
||||
|
||||
// (3) Not sleeping -> poll tasks
|
||||
try
|
||||
{
|
||||
const std::string path = "/api/tasks/" + guid;
|
||||
spdlog::info("GET {}", path);
|
||||
auto res = m_taskClient->Get(path.c_str());
|
||||
if (!res)
|
||||
{
|
||||
spdlog::warn("GET {} failed (no response)", path);
|
||||
m_taskClient.reset(); // force rebuild next iter
|
||||
}
|
||||
else if (res->status == 204)
|
||||
{
|
||||
spdlog::debug("No tasks (204).");
|
||||
}
|
||||
else if (res->status >= 200 && res->status < 300)
|
||||
{
|
||||
auto tasks = ParseTasks(res->body);
|
||||
for (const auto &t : tasks)
|
||||
{
|
||||
auto handler = ResolveHandler(t.type);
|
||||
auto [ok, resultJson, err] = handler(t);
|
||||
PostResult(*m_taskClient, guid, t.id, ok, resultJson, err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::warn("GET {} -> HTTP {}, body: {}", path, res->status, res->body);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
spdlog::error("Task loop error: {}", e.what());
|
||||
m_taskClient.reset();
|
||||
}
|
||||
|
||||
// (4) Backoff before next poll
|
||||
SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user