modified config and created enrolment service and device control service

This commit is contained in:
tdv
2025-10-09 15:11:54 +03:00
parent 490fc11f6a
commit 5af104acf5
10 changed files with 706 additions and 26 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@
/toolchain/sysroot/
/toolchain/build/
.vscode/

View File

@@ -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} )

View File

@@ -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
}

39
gen_device_csr.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
set -euo pipefail
if [ $# -ne 1 ]; then
echo "Usage: $0 <DEVICE_GUID>"
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" <<EOF
[ req ]
default_md = sha256
prompt = no
distinguished_name = dn
req_extensions = req_ext
[ dn ]
CN = $GUID
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
URI.1 = urn:device:$GUID
EOF
# Generate private key
openssl ecparam -name prime256v1 -genkey -noout -out "$KEY_FILE"
chmod 600 "$KEY_FILE"
# Generate CSR
openssl req -new -key "$KEY_FILE" -out "$CSR_FILE" -config "$CONF_FILE"

11
key-load.service Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=Load IoT TLS key into kernel keyring
After=network-pre.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/load-iot-key.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

20
load-iot-key.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
CPU_SERIAL=$(awk '/Serial/ {print $3}' /proc/cpuinfo)
KEK=$(echo -n "$CPU_SERIAL" | \
openssl dgst -sha256 -hmac "server-provided-salt" | \
awk '{print $2}')
# Decrypt into tmpfs
mkdir -p /run/iot
openssl enc -d -aes-256-gcm -pbkdf2 \
-pass pass:$KEK \
-in /etc/iot/keys/device.key.enc \
-out /run/iot/device.key
# Load into kernel keyring (root-only key)
keyctl padd user iot-client-key @s < /run/iot/device.key
# Securely erase plaintext
shred -u /run/iot/device.key

View File

@@ -15,9 +15,11 @@ public:
std::string m_guid;
unsigned long long m_recordingDuration = 0;
std::string m_baseUrl;
unsigned int m_polling = 120;
unsigned int m_jitter = 10;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Config, m_guid, m_recordingDuration, m_baseUrl )
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Config, m_guid, m_recordingDuration, m_baseUrl, m_polling, m_jitter)
class ConfigService {
std::shared_ptr<Config> 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;

View File

@@ -0,0 +1,295 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <map>
#include <thread>
#include <atomic>
#include <random>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <optional>
#include <regex>
#include <array>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#include <httplib.h> // build with CPPHTTPLIB_OPENSSL_SUPPORT
#include <sys/wait.h> // 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<bool, std::string, std::string>;
using TaskHandler = std::function<HandlerResult(const Task&)>;
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<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() {
m_stop = true;
if (m_thread.joinable()) m_thread.join();
}
private:
std::shared_ptr<ConfigService> m_cfg;
Handlers m_handlers;
std::thread m_thread;
std::atomic<bool> 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<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;
}
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<httplib::Client> 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<httplib::SSLClient>(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<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
void SleepWithJitterOnce(std::mt19937& rng, int baseSec, int jitterSec) {
if (baseSec < 0) baseSec = 0;
if (jitterSec < 0) jitterSec = 0;
std::uniform_int_distribution<int> 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<Task> ParseTasks(const std::string& body) {
// server might return single object or array
std::vector<Task> 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<std::string>()); }
catch (...) { t.payload = raw.get<std::string>(); }
} 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<std::filesystem::path> 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

View File

@@ -0,0 +1,203 @@
// src/Services/EnrollmentService.h
#pragma once
#include <string>
#include <vector>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <regex>
#include <memory>
#include <spdlog/spdlog.h>
#include <httplib.h>
#include <stdio.h>
#include <sys/wait.h>
#include "ConfigService.h"
namespace snoop {
class EnrollmentService {
std::shared_ptr<ConfigService> m_cfg;
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());
}
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<ConfigService> 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 <GUID>
{
// 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<std::string>());
if (j.contains("issuing_ca")) WriteFile(caPath, j["issuing_ca"].get<std::string>());
if (j.contains("ca_chain")) WriteFile(chainPath, j["ca_chain"].get<std::string>());
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

View File

@@ -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<AudioEncoderConcept TAudioEncoder, AudioWriterConcept TAudioWriter>
void Main() {
try {
namespace snoop
{
template <AudioEncoderConcept TAudioEncoder, AudioWriterConcept TAudioWriter>
void Main()
{
try
{
int sampleRate = 48000;
int channels = 1;
int framesPerBuffer = 2880;
auto configService = std::make_shared<ConfigService>( "config.json" );
auto configService = std::make_shared<ConfigService>("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{};
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<unsigned long long>(t.payload.at("duration").get<int>() * 1000ULL);
configService->SetRecordingDuration(ms);
}
if (t.payload.contains("sleep"))
{
configService->SetPollingInterwall(t.payload.at("sleep").get<int>());
}
if (t.payload.contains("jitter"))
{
configService->SetJitter(t.payload.at("jitter").get<int>());
}
if (t.payload.contains("endpoint"))
{
configService->SetBaseUrl(t.payload.at("endpoint").get<std::string>());
}
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"})", ""};
};
static std::unique_ptr<snoop::DeviceControlService> g_taskSvc;
g_taskSvc = std::make_unique<snoop::DeviceControlService>(configService, handlers);
}
auto sioClient = std::make_shared<sio::client>();
sioClient->connect( configService->GetBaseUrl() );
sioClient->connect(configService->GetBaseUrl());
auto writerService = std::make_shared<AudioWriterService>( configService, "records" );
AudioStreamService streamer( sioClient, configService->GetGuid() );
TAudioEncoder encoder( sampleRate, channels, framesPerBuffer, [&]( auto input, auto size ) -> int {
auto writerService = std::make_shared<AudioWriterService>(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 );
while (true)
{
sleep(1000);
}
}
catch (const std::exception &ex)
{
spdlog::error("Exception: {}", ex.what());
}
} catch( const std::exception& ex ) {
spdlog::error( "Exception: {}", ex.what() );
}
}
}
int main() {
int main()
{
snoop::Main<snoop::OpusEncoder<AudioAdapter>, snoop::OggAudioWriter>();
return 0;
}