From 5af104acf5c2b8745ed4cfc2060026c35ca48f93 Mon Sep 17 00:00:00 2001 From: tdv Date: Thu, 9 Oct 2025 15:11:54 +0300 Subject: [PATCH] modified config and created enrolment service and device control service --- .gitignore | 1 + CMakeLists.txt | 2 + config.json | 4 +- gen_device_csr.sh | 39 ++++ key-load.service | 11 ++ load-iot-key.sh | 20 ++ src/Services/ConfigService.h | 27 ++- src/Services/DeviceControlService.h | 295 ++++++++++++++++++++++++++++ src/Services/EnrollmentService.h | 203 +++++++++++++++++++ src/main.cpp | 130 +++++++++--- 10 files changed, 706 insertions(+), 26 deletions(-) create mode 100644 gen_device_csr.sh create mode 100644 key-load.service create mode 100644 load-iot-key.sh create mode 100644 src/Services/DeviceControlService.h create mode 100644 src/Services/EnrollmentService.h diff --git a/.gitignore b/.gitignore index cda78e1..0c1cf50 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /toolchain/sysroot/ /toolchain/build/ +.vscode/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c8bbc43..b0834fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ set( HEADERS src/Services/AudioStreamService.h src/Services/AudioWriterService.h src/Services/ConfigService.h + src/Services/DeviceControlService.h + src/Services/EnrollmentService.h ) add_executable( ${PROJECT_NAME} ${SOURCES} ${HEADERS} ) diff --git a/config.json b/config.json index 47cb888..0a2aedf 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,7 @@ { "m_guid": "123e4567-e89b-12d3-a456-426614174000", "m_recordingDuration": 10000, - "m_baseUrl": "http://localhost:3000" + "m_baseUrl": "http://localhost:3000", + "m_polling": 120, + "m_jitter": 10 } \ No newline at end of file diff --git a/gen_device_csr.sh b/gen_device_csr.sh new file mode 100644 index 0000000..0a74bec --- /dev/null +++ b/gen_device_csr.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +GUID="$1" + +# Output files +KEY_FILE="device_${GUID}.key" +CSR_FILE="device_${GUID}.csr" +CONF_FILE="csr_${GUID}.conf" + +# Generate config for CSR +cat > "$CONF_FILE" < m_config; @@ -52,6 +54,29 @@ public: return this->m_config->m_baseUrl; } + void SetBaseUrl(std::string newBaseUrl) { + this->m_config->m_baseUrl = newBaseUrl; + this->RewriteConfig(); + } + + [[nodiscard]] unsigned int GetPollingInterwall() { + return this->m_config->m_polling; + } + + void SetPollingInterwall(unsigned int newPollInterwall) { + this->m_config->m_polling = newPollInterwall; + this->RewriteConfig(); + } + + [[nodscard]] unsigned int GetJitter() { + return this->m_config->m_jitter; + } + + void SetJitter(unsigned int newJitter) { + this->m_config->m_jitter = newJitter; + this -> RewriteConfig(); + } + private: std::string ReadFile( const std::string& path ) { std::fstream f; diff --git a/src/Services/DeviceControlService.h b/src/Services/DeviceControlService.h new file mode 100644 index 0000000..d5753f1 --- /dev/null +++ b/src/Services/DeviceControlService.h @@ -0,0 +1,295 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include // build with CPPHTTPLIB_OPENSSL_SUPPORT +#include // for WEXITSTATUS + +#include "ConfigService.h" + +namespace snoop { + +class DeviceControlService { +public: + struct Task { + uint64_t id{}; + std::string type; + nlohmann::json payload = nlohmann::json::object(); + }; + + // Handler returns: {success, result_json_string, error_message} + using HandlerResult = std::tuple; + using TaskHandler = std::function; + + struct Handlers { + // Fill any you implement. Unset => default “not implemented”. + TaskHandler onStartStream; + TaskHandler onStopStream; + TaskHandler onStartRecording; + TaskHandler onStopRecording; + TaskHandler onUpdateConfig; + TaskHandler onSetDeepSleep; + }; + + DeviceControlService(std::shared_ptr cfg, Handlers handlers) + : m_cfg(std::move(cfg)), m_handlers(std::move(handlers)) { + m_stop = false; + m_thread = std::thread(&DeviceControlService::RunLoop, this); + } + + ~DeviceControlService() { + m_stop = true; + if (m_thread.joinable()) m_thread.join(); + } + +private: + std::shared_ptr m_cfg; + Handlers m_handlers; + std::thread m_thread; + std::atomic m_stop{false}; + + // --- 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 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; + } + + struct Url { + std::string scheme; // http/https + std::string host; // hostname or ip + int port = 0; // default if 0 + }; + + static Url ParseBase(const std::string& base) { + // very small parser: scheme://host[:port] + std::regex re(R"(^\s*(https?)://([^/:]+)(?::(\d+))?\s*$)"); + std::smatch m; + if (!std::regex_match(base, m, re)) { + throw std::runtime_error("Invalid base URL for DeviceControlService: " + base); + } + Url u; + u.scheme = m[1].str(); + u.host = m[2].str(); + u.port = m[3].matched ? std::stoi(m[3].str()) : (u.scheme == "https" ? 443 : 80); + return u; + } + + // 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-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; + } + + // Create HTTPS client configured for mTLS (or HTTP if base is http) + std::unique_ptr MakeClient(const Url& u, + const std::filesystem::path& ca, + const std::filesystem::path& crt, + const std::filesystem::path& key) { + if (u.scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + auto cli = std::make_unique(u.host.c_str(), u.port); + cli->enable_server_certificate_verification(true); + cli->set_ca_cert_path(ca.string().c_str()); + cli->set_client_cert_file(crt.string().c_str(), key.string().c_str(), nullptr); + // Recommended timeouts for long polling-ish flows + cli->set_connection_timeout(10); + cli->set_read_timeout(60); + cli->set_write_timeout(60); + return cli; +#else + throw std::runtime_error("CPPHTTPLIB_OPENSSL_SUPPORT not enabled but https URL provided"); +#endif + } else { + auto cli = std::make_unique(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 + void SleepWithJitterOnce(std::mt19937& rng, int baseSec, int jitterSec) { + if (baseSec < 0) baseSec = 0; + if (jitterSec < 0) jitterSec = 0; + std::uniform_int_distribution dist(-jitterSec, +jitterSec); + int delay = baseSec + dist(rng); + if (delay < 0) delay = 0; + std::this_thread::sleep_for(std::chrono::seconds(delay)); + } + + // default fallback if handler is not provided + static HandlerResult NotImplemented(const Task& t) { + std::string msg = "Handler not implemented for type: " + t.type; + spdlog::warn("{}", msg); + return {false, "{}", msg}; + } + + TaskHandler ResolveHandler(std::string_view type) const { + if (type == "start_stream") return m_handlers.onStartStream ? m_handlers.onStartStream : NotImplemented; + if (type == "stop_stream") return m_handlers.onStopStream ? m_handlers.onStopStream : NotImplemented; + if (type == "start_recording") return m_handlers.onStartRecording ? m_handlers.onStartRecording : NotImplemented; + if (type == "stop_recording") return m_handlers.onStopRecording ? m_handlers.onStopRecording : NotImplemented; + if (type == "update_config") return m_handlers.onUpdateConfig ? m_handlers.onUpdateConfig : NotImplemented; + if (type == "set_deep_sleep") return m_handlers.onSetDeepSleep ? m_handlers.onSetDeepSleep : NotImplemented; + return NotImplemented; + } + + static std::vector ParseTasks(const std::string& body) { + // server might return single object or array + std::vector tasks; + auto j = nlohmann::json::parse(body); + + auto push_one = [&](const nlohmann::json& x){ + Task t; + t.id = x.value("id", 0); + t.type = x.value("type", ""); + try { + if (x.contains("payload")) { + const auto& raw = x.at("payload"); + if (raw.is_string()) { + // payload is quoted JSON string -> parse inner if valid, else keep as string + try { t.payload = nlohmann::json::parse(raw.get()); } + catch (...) { t.payload = raw.get(); } + } else if (raw.is_object() || raw.is_array()) { + t.payload = raw; + } else { + t.payload = nlohmann::json::object(); + } + } + } catch (...) { t.payload = nlohmann::json::object(); } + tasks.push_back(std::move(t)); + }; + + if (j.is_array()) { + for (auto& it : j) push_one(it); + } else if (j.is_object()) { + push_one(j); + } + return tasks; + } + + void PostResult(httplib::Client& cli, const std::string& guid, + uint64_t taskId, bool success, + const std::string& resultJson, const std::string& err) { + nlohmann::json dto = { + {"taskId", taskId}, + {"success", success}, + {"result", resultJson.empty() ? "{}" : resultJson}, + {"error", err} + }; + std::string path = "/api/tasks/" + guid; + auto res = cli.Post(path.c_str(), dto.dump(), "application/json"); + if (!res) { + spdlog::error("POST {} failed (no response)", path); + return; + } + spdlog::info("POST {} -> HTTP {}", path, res->status); + } + + 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 tmpKey; + try { + tmpKey = ExtractClientKeyFromKernelKeyring(); + } catch (const std::exception& e) { + spdlog::error("Key extraction failed: {}", e.what()); + SleepWithJitterOnce(rng, m_cfg->GetPollingSeconds(), m_cfg->GetJitterSeconds()); + continue; + } + + 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->GetPollingSeconds(), m_cfg->GetJitterSeconds()); + } + } +}; + +} // namespace snoop diff --git a/src/Services/EnrollmentService.h b/src/Services/EnrollmentService.h new file mode 100644 index 0000000..6de7e13 --- /dev/null +++ b/src/Services/EnrollmentService.h @@ -0,0 +1,203 @@ +// src/Services/EnrollmentService.h +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ConfigService.h" + +namespace snoop { + +class EnrollmentService { + std::shared_ptr m_cfg; + + static std::string Exec(const std::string& cmd) { + std::array 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()); + } + auto rc = pclose(pipe); + int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc; + if (exitCode != 0) + spdlog::warn("Command '{}' exited with code {}", cmd, exitCode); + return out; + } + + static void WriteFile(const std::filesystem::path& p, const std::string& data, bool createParents=true) { + if (createParents) std::filesystem::create_directories(p.parent_path()); + std::ofstream f(p, std::ios::binary); + if (!f) throw std::runtime_error("Cannot open file for write: " + p.string()); + f.write(data.data(), (std::streamsize)data.size()); + } + + static std::string ReadFile(const std::filesystem::path& p) { + std::ifstream f(p, std::ios::binary); + if (!f) throw std::runtime_error("Cannot open file for read: " + p.string()); + std::ostringstream ss; ss << f.rdbuf(); + return ss.str(); + } + + 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 ParseCpuSerial() { + // Default for “empty” case: 12 times '9' + std::string fallback(12, '9'); + + std::ifstream f("/proc/cpuinfo"); + if (!f) { + spdlog::warn("/proc/cpuinfo not available, using fallback serial"); + return fallback; + } + std::string line; + std::regex re(R"(^\s*Serial\s*:\s*([0-9A-Fa-f]+)\s*$)"); + while (std::getline(f, line)) { + std::smatch m; + if (std::regex_match(line, m, re) && m.size() == 2) { + auto serial = Trim(m[1].str()); + if (!serial.empty()) return serial; + } + } + spdlog::warn("CPU Serial not found, using fallback"); + return fallback; + } + +public: + explicit EnrollmentService(std::shared_ptr cfg) : m_cfg(std::move(cfg)) {} + + // Returns true if enrollment was performed now; false if it was already done + bool EnsureEnrolled() { + const std::string guid = m_cfg->GetGuid(); + const std::filesystem::path keystoreDir = "/etc/iot/keys"; + const auto encKeyPath = keystoreDir / "device.key.enc"; + const auto certPath = keystoreDir / "device.crt.pem"; + const auto caPath = keystoreDir / "issuing_ca.pem"; + const auto chainPath = keystoreDir / "ca_chain.pem"; + + // If we already have encrypted key + cert, assume done + if (std::filesystem::exists(encKeyPath) && + std::filesystem::exists(certPath)) { + spdlog::info("Enrollment already completed."); + return false; + } + + spdlog::info("Starting first-run enrollment..."); + + // 1) Run gen_device_csr.sh + { + // Assumes script is in the working dir or in PATH + const std::string cmd = "bash ./gen_device_csr.sh " + guid; + spdlog::info("Executing: {}", cmd); + auto out = Exec(cmd); + spdlog::debug("gen_device_csr.sh output:\n{}", out); + } + const std::string keyName = "device_" + guid + ".key"; + const std::string csrName = "device_" + guid + ".csr"; + + if (!std::filesystem::exists(keyName) || !std::filesystem::exists(csrName)) { + throw std::runtime_error("CSR or key was not generated by gen_device_csr.sh"); + } + + // 2) CPU serial (awk '/Serial/ {print $3}' /proc/cpuinfo), if empty -> 9 * 12 + const std::string cpuSerial = ParseCpuSerial(); + spdlog::info("CPU_SERIAL = {}", cpuSerial); + + // 3) KEK = HMAC-SHA256(cpuSerial, key=GUID) (bash equiv used) + std::string kek; + { + // careful to avoid trailing newline + const std::string cmd = "printf %s \"" + cpuSerial + "\"" + " | openssl dgst -sha256 -hmac \"" + guid + "\" | awk '{print $2}'"; + spdlog::info("Deriving KEK with HMAC-SHA256"); + kek = Trim(Exec(cmd)); + if (kek.empty()) throw std::runtime_error("Failed to derive KEK"); + spdlog::debug("KEK (hex) = {}", kek); + } + + // 4) Encrypt the private key to /etc/iot/keys/device.key.enc using KEK; shred original + { + std::filesystem::create_directories(keystoreDir); + const std::string cmd = + "openssl enc -aes-256-gcm -pbkdf2 -salt " + "-pass pass:" + kek + " " + "-in " + keyName + " " + "-out " + encKeyPath.string() + " " + "&& shred -u " + keyName; + spdlog::info("Encrypting private key and shredding plaintext..."); + auto out = Exec(cmd); + spdlog::debug("openssl enc output:\n{}", out); + + if (!std::filesystem::exists(encKeyPath)) { + throw std::runtime_error("Encrypted key not created: " + encKeyPath.string()); + } + } + + // 5) Send CSR to /enroll/:guid as multipart form, field `json: {"csr":"<...>"}` + std::string csrPem = ReadFile(csrName); + { + httplib::Client cli(m_cfg->GetBaseUrl()); + httplib::MultipartFormDataItems items = { + { "json", std::string("{\"csr\":\"") + EscapeJsonForOneLine(csrPem) + "\"}", "enroll.json", "application/json" } + }; + + const std::string path = "/api/enroll/" + guid; + spdlog::info("POST {} (multipart)", path); + auto res = cli.Post(path.c_str(), items); + if (!res) throw std::runtime_error("Enroll request failed (no response)"); + spdlog::info("Enroll response status: {}", res->status); + if (res->status != 200 && res->status != 201) + throw std::runtime_error("Enroll failed: HTTP " + std::to_string(res->status)); + + // 6) Expect JSON: { "certificate": "...", "issuing_ca":"...", "ca_chain":"..." } + nlohmann::json j = nlohmann::json::parse(res->body); + if (!j.contains("certificate")) + throw std::runtime_error("Enroll response missing 'certificate'"); + + WriteFile(certPath, j["certificate"].get()); + if (j.contains("issuing_ca")) WriteFile(caPath, j["issuing_ca"].get()); + if (j.contains("ca_chain")) WriteFile(chainPath, j["ca_chain"].get()); + spdlog::info("Enrollment artifacts saved to {}", keystoreDir.string()); + } + + // (Optional) cleanup CSR after successful enrollment + try { std::filesystem::remove(csrName); } catch (...) {} + + return true; + } + +private: + // very small helper to pack PEM into JSON string value (single-line, escaped quotes & newlines) + static std::string EscapeJsonForOneLine(const std::string& in) { + std::string out; out.reserve(in.size()*2); + for (char c : in) { + switch (c) { + case '\\': out += "\\\\"; break; + case '\"': out += "\\\""; break; + case '\n': out += "\\n"; break; + case '\r': /* skip */ break; + default: out += c; break; + } + } + return out; + } +}; + +} // namespace snoop diff --git a/src/main.cpp b/src/main.cpp index eaea209..1134ef2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,8 @@ #include "Services/AudioStreamService.h" #include "Services/AudioWriterService.h" #include "Services/ConfigService.h" +#include "Services/EnrollmentService.h" +#include "Services/DeviceControlService.h" #ifdef USE_ALSA_ADAPTER #include "AudioAdapters/AlsaAudioAdapter.h" @@ -17,42 +19,122 @@ using AudioAdapter = snoop::AlsaAudioAdapter; using AudioAdapter = snoop::PortAudioAdapter; #endif - // sudo apt-get install libasound2-dev +namespace snoop +{ + template + void Main() + { + try + { + int sampleRate = 48000; + int channels = 1; + int framesPerBuffer = 2880; -namespace snoop { -template -void Main() { - try { - int sampleRate = 48000; - int channels = 1; - int framesPerBuffer = 2880; + auto configService = std::make_shared("config.json"); - auto configService = std::make_shared( "config.json" ); + // ---- FIRST-RUN ENROLLMENT ---- + { + EnrollmentService enroll(configService); + const bool didEnroll = enroll.EnsureEnrolled(); + if (didEnroll) + { + spdlog::info("First-run enrollment completed."); + } + } + // ------------------------------ + { + snoop::DeviceControlService::Handlers handlers{}; - auto sioClient = std::make_shared(); - sioClient->connect( configService->GetBaseUrl() ); + handlers.onStartStream = [](const snoop::DeviceControlService::Task &t) + { + spdlog::info("start_stream payload: {}", t.payload.dump()); + // TODO: start your streaming pipeline using payload["whipUrl"], etc. + return snoop::DeviceControlService::HandlerResult{true, R"({"status":"started"})", ""}; + }; + handlers.onStopStream = [](const snoop::DeviceControlService::Task &t) + { + spdlog::info("stop_stream payload: {}", t.payload.dump()); + // TODO: stop streaming + return snoop::DeviceControlService::HandlerResult{true, R"({"status":"stopped"})", ""}; + }; + handlers.onStartRecording = [&](const snoop::DeviceControlService::Task &t) + { + spdlog::info("start_recording payload: {}", t.payload.dump()); + // TODO: if you gate writes in AudioWriterService, flip to recording mode here + return snoop::DeviceControlService::HandlerResult{true, R"({"recording":"on"})", ""}; + }; + handlers.onStopRecording = [&](const snoop::DeviceControlService::Task &t) + { + spdlog::info("stop_recording payload: {}", t.payload.dump()); + // TODO: flip to recording off + return snoop::DeviceControlService::HandlerResult{true, R"({"recording":"off"})", ""}; + }; + handlers.onUpdateConfig = [&](const snoop::DeviceControlService::Task &t) + { + spdlog::info("update_config payload: {}", t.payload.dump()); + try + { + if (t.payload.contains("duration")) + { + auto ms = static_cast(t.payload.at("duration").get() * 1000ULL); + configService->SetRecordingDuration(ms); + } + if (t.payload.contains("sleep")) + { + configService->SetPollingInterwall(t.payload.at("sleep").get()); + } + if (t.payload.contains("jitter")) + { + configService->SetJitter(t.payload.at("jitter").get()); + } + if (t.payload.contains("endpoint")) + { + configService->SetBaseUrl(t.payload.at("endpoint").get()); + } + return snoop::DeviceControlService::HandlerResult{true, R"({"updated":true})", ""}; + } + catch (const std::exception &e) + { + return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()}; + } + }; + handlers.onSetDeepSleep = [](const snoop::DeviceControlService::Task &t) + { + spdlog::info("set_deep_sleep payload: {}", t.payload.dump()); + // TODO: use platform power management or just extend sleep window + return snoop::DeviceControlService::HandlerResult{true, R"({"sleep":"scheduled"})", ""}; + }; - auto writerService = std::make_shared( configService, "records" ); - AudioStreamService streamer( sioClient, configService->GetGuid() ); - TAudioEncoder encoder( sampleRate, channels, framesPerBuffer, [&]( auto input, auto size ) -> int { + static std::unique_ptr g_taskSvc; + g_taskSvc = std::make_unique(configService, handlers); + } + auto sioClient = std::make_shared(); + sioClient->connect(configService->GetBaseUrl()); + + auto writerService = std::make_shared(configService, "records"); + AudioStreamService streamer(sioClient, configService->GetGuid()); + TAudioEncoder encoder(sampleRate, channels, framesPerBuffer, [&](auto input, auto size) -> int + { streamer.SendAudioData( input, size ); writerService->WriteAudioData( input, size, framesPerBuffer ); - return paContinue; - } ); + return paContinue; }); - while( true ) { - sleep( 1000 ); - } - - } catch( const std::exception& ex ) { - spdlog::error( "Exception: {}", ex.what() ); + while (true) + { + sleep(1000); + } + } + catch (const std::exception &ex) + { + spdlog::error("Exception: {}", ex.what()); + } } } -} -int main() { +int main() +{ snoop::Main, snoop::OggAudioWriter>(); return 0; } \ No newline at end of file