fixed audiostream, tasks andd other shit
This commit is contained in:
@@ -60,17 +60,17 @@ public:
|
|||||||
std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
|
||||||
|
|
||||||
// extract client key via keyctl
|
// extract client key via keyctl
|
||||||
auto tmpKey = snoop::device_sec::ExtractClientKeyFromKernelKeyring();
|
// auto tmpKey = snoop::device_sec::ExtractClientKeyFromKernelKeyring();
|
||||||
if (!tmpKey.string().empty()) {
|
// if (!tmpKey.string().empty()) {
|
||||||
spdlog::error("Cannot extract client key for WHIP (keyctl user iot-client-key)");
|
// spdlog::error("Cannot extract client key for WHIP (keyctl user iot-client-key)");
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
WhipClient::Params p{
|
WhipClient::Params p{
|
||||||
.whipUrl = whipUrl,
|
.whipUrl = whipUrl,
|
||||||
.caPath = ca.string(),
|
.caPath = ca.string(),
|
||||||
.crtPath = crt.string(),
|
.crtPath = crt.string(),
|
||||||
.keyPath = tmpKey,
|
// .keyPath = tmpKey,
|
||||||
.sampleRate= sampleRate,
|
.sampleRate= sampleRate,
|
||||||
.channels = channels
|
.channels = channels
|
||||||
};
|
};
|
||||||
@@ -78,11 +78,11 @@ public:
|
|||||||
try {
|
try {
|
||||||
m_whip->Start();
|
m_whip->Start();
|
||||||
spdlog::info("WHIP started");
|
spdlog::info("WHIP started");
|
||||||
m_tmpKeyPath = tmpKey;
|
// m_tmpKeyPath = tmpKey;
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("WHIP start failed: {}", e.what());
|
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();
|
m_whip.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ namespace snoop
|
|||||||
{"stoppedAt", std::to_string(stoppedAt), "", "text/plain"},
|
{"stoppedAt", std::to_string(stoppedAt), "", "text/plain"},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res = client.Post("/api/records/upload/", items);
|
auto res = client.Post("/api/records/upload", items);
|
||||||
if (res && (res->status == 201 || res->status == 200))
|
if (res && (res->status == 201 || res->status == 200))
|
||||||
{
|
{
|
||||||
spdlog::info("File uploaded successfully: HTTP {}", res->status);
|
spdlog::info("File uploaded successfully: HTTP {}", res->status);
|
||||||
|
|||||||
@@ -248,9 +248,13 @@ namespace snoop
|
|||||||
// default fallback if handler is not provided
|
// default fallback if handler is not provided
|
||||||
static HandlerResult NotImplemented(const Task &t)
|
static HandlerResult NotImplemented(const Task &t)
|
||||||
{
|
{
|
||||||
std::string msg = "Handler not implemented for type: " + t.type;
|
std::string msg = "Handler not implemented for type: " + (t.type.empty() ? "<empty>" : t.type);
|
||||||
spdlog::warn("{}", msg);
|
spdlog::warn("{}", msg);
|
||||||
return {false, "{}", msg};
|
// Include echo of payload for debugging
|
||||||
|
nlohmann::json res = {
|
||||||
|
{"type", t.type},
|
||||||
|
{"payload", t.payload}};
|
||||||
|
return {false, res.dump(), msg};
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskHandler ResolveHandler(std::string_view type) const
|
TaskHandler ResolveHandler(std::string_view type) const
|
||||||
@@ -272,15 +276,37 @@ namespace snoop
|
|||||||
|
|
||||||
static std::vector<Task> ParseTasks(const std::string &body)
|
static std::vector<Task> ParseTasks(const std::string &body)
|
||||||
{
|
{
|
||||||
// server might return single object or array
|
std::vector<Task> out;
|
||||||
std::vector<Task> tasks;
|
nlohmann::json j = nlohmann::json::parse(body);
|
||||||
auto j = nlohmann::json::parse(body);
|
|
||||||
|
|
||||||
auto push_one = [&](const nlohmann::json &x)
|
auto to_id = [](const nlohmann::json &v) -> uint64_t
|
||||||
|
{
|
||||||
|
if (v.is_number_unsigned())
|
||||||
|
return v.get<uint64_t>();
|
||||||
|
if (v.is_number_integer())
|
||||||
|
return static_cast<uint64_t>(v.get<long long>());
|
||||||
|
if (v.is_string())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return static_cast<uint64_t>(std::stoull(v.get<std::string>()));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0ULL;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_one = [&](const nlohmann::json &x)
|
||||||
{
|
{
|
||||||
Task t;
|
Task t;
|
||||||
t.id = x.value("id", 0);
|
if (x.contains("id"))
|
||||||
t.type = x.value("type", "");
|
t.id = to_id(x["id"]);
|
||||||
|
if (x.contains("type") && x["type"].is_string())
|
||||||
|
t.type = x["type"].get<std::string>();
|
||||||
|
|
||||||
|
// payload can be stringified JSON or object
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (x.contains("payload"))
|
if (x.contains("payload"))
|
||||||
@@ -288,7 +314,6 @@ namespace snoop
|
|||||||
const auto &raw = x.at("payload");
|
const auto &raw = x.at("payload");
|
||||||
if (raw.is_string())
|
if (raw.is_string())
|
||||||
{
|
{
|
||||||
// payload is quoted JSON string -> parse inner if valid, else keep as string
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
t.payload = nlohmann::json::parse(raw.get<std::string>());
|
t.payload = nlohmann::json::parse(raw.get<std::string>());
|
||||||
@@ -312,38 +337,66 @@ namespace snoop
|
|||||||
{
|
{
|
||||||
t.payload = nlohmann::json::object();
|
t.payload = nlohmann::json::object();
|
||||||
}
|
}
|
||||||
tasks.push_back(std::move(t));
|
|
||||||
|
if (t.id == 0)
|
||||||
|
spdlog::warn("Task without valid id: {}", x.dump());
|
||||||
|
if (t.type.empty())
|
||||||
|
spdlog::warn("Task without type: {}", x.dump());
|
||||||
|
out.push_back(std::move(t));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (j.is_array())
|
if (j.is_array())
|
||||||
{
|
{
|
||||||
for (auto &it : j)
|
for (const auto &it : j)
|
||||||
push_one(it);
|
parse_one(it);
|
||||||
}
|
}
|
||||||
else if (j.is_object())
|
else if (j.is_object())
|
||||||
{
|
{
|
||||||
push_one(j);
|
// NEW: wrapper shape { hasTask: bool, task: { ... } }
|
||||||
|
if (j.contains("task") && j["task"].is_object())
|
||||||
|
{
|
||||||
|
// If server includes a 'status' gate and you only want runnable tasks, you can check here:
|
||||||
|
// if (j.value("hasTask", false) == true)
|
||||||
|
parse_one(j["task"]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// bare single task
|
||||||
|
parse_one(j);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tasks;
|
else
|
||||||
|
{
|
||||||
|
spdlog::warn("Unexpected tasks JSON: {}", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostResult(httplib::SSLClient &cli, const std::string &guid,
|
void PostResult(httplib::SSLClient &cli, const std::string &guid,
|
||||||
uint64_t taskId, bool success,
|
uint64_t taskId, bool success,
|
||||||
const std::string &resultJson, const std::string &err)
|
const std::string &resultJson, const std::string &err)
|
||||||
{
|
{
|
||||||
|
if (taskId == 0)
|
||||||
|
{
|
||||||
|
spdlog::warn("Skip posting result: invalid taskId=0 (success={}, err={})", success, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
nlohmann::json dto = {
|
nlohmann::json dto = {
|
||||||
{"taskId", taskId},
|
{"taskId", taskId},
|
||||||
{"success", success},
|
{"success", success},
|
||||||
{"result", resultJson.empty() ? "{}" : resultJson},
|
{"result", resultJson.empty() ? "{}" : resultJson},
|
||||||
{"error", err}};
|
{"error", err}};
|
||||||
std::string path = "/api/tasks/" + guid;
|
std::string path = "/api/tasks/" + guid;
|
||||||
auto res = cli.Post(path.c_str(), dto.dump(), "application/json");
|
auto body = dto.dump();
|
||||||
|
spdlog::debug("POST {} body: {}", path, body);
|
||||||
|
auto res = cli.Post(path.c_str(), body, "application/json");
|
||||||
if (!res)
|
if (!res)
|
||||||
{
|
{
|
||||||
spdlog::error("POST {} failed (no response)", path);
|
spdlog::error("POST {} failed (no response)", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
spdlog::info("POST {} -> HTTP {}", path, res->status);
|
spdlog::info("POST {} -> HTTP {} body: {}", path, res->status, res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnterDeepSleepUntil(long long stopMs)
|
void EnterDeepSleepUntil(long long stopMs)
|
||||||
@@ -504,6 +557,11 @@ namespace snoop
|
|||||||
{
|
{
|
||||||
auto handler = ResolveHandler(t.type);
|
auto handler = ResolveHandler(t.type);
|
||||||
auto [ok, resultJson, err] = handler(t);
|
auto [ok, resultJson, err] = handler(t);
|
||||||
|
if (t.id == 0)
|
||||||
|
{
|
||||||
|
spdlog::warn("Won't POST result for task with id=0: type='{}'", t.type);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
PostResult(*m_taskClient, guid, t.id, ok, resultJson, err);
|
PostResult(*m_taskClient, guid, t.id, ok, resultJson, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <rtc/peerconnection.hpp>
|
#include <rtc/peerconnection.hpp>
|
||||||
#include <rtc/description.hpp>
|
#include <rtc/description.hpp>
|
||||||
#include <rtc/track.hpp>
|
#include <rtc/track.hpp>
|
||||||
|
#include "Security/TlsKeyUtil.h"
|
||||||
|
|
||||||
namespace snoop
|
namespace snoop
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ namespace snoop
|
|||||||
std::string whipUrl; // full WHIP endpoint (may include ?token=...)
|
std::string whipUrl; // full WHIP endpoint (may include ?token=...)
|
||||||
std::string caPath; // CA chain
|
std::string caPath; // CA chain
|
||||||
std::string crtPath; // client cert
|
std::string crtPath; // client cert
|
||||||
std::string keyPath; // client key (temp extracted from keyctl)
|
// std::string keyPath; // client key (temp extracted from keyctl)
|
||||||
int sampleRate = 48000;
|
int sampleRate = 48000;
|
||||||
int channels = 1; // 1 or 2; timestamp advance uses PCM frames per channel
|
int channels = 1; // 1 or 2; timestamp advance uses PCM frames per channel
|
||||||
};
|
};
|
||||||
@@ -79,6 +80,15 @@ namespace snoop
|
|||||||
// Opus PT typically negotiated to 111 by browsers; we'll use 111 in RTP header
|
// Opus PT typically negotiated to 111 by browsers; we'll use 111 in RTP header
|
||||||
m_track = m_pc->addTrack(audioDesc);
|
m_track = m_pc->addTrack(audioDesc);
|
||||||
|
|
||||||
|
// IMPORTANT: wait for SRTP sender to be ready
|
||||||
|
m_track->onOpen([this] {
|
||||||
|
spdlog::info("WHIP track opened");
|
||||||
|
m_trackOpen = true;
|
||||||
|
});
|
||||||
|
m_track->onClosed([this] {
|
||||||
|
spdlog::info("WHIP track closed");
|
||||||
|
m_trackOpen = false;
|
||||||
|
});
|
||||||
// Initialize RTP state (random SSRC/seq)
|
// Initialize RTP state (random SSRC/seq)
|
||||||
std::mt19937 rng{std::random_device{}()};
|
std::mt19937 rng{std::random_device{}()};
|
||||||
m_ssrc = std::uniform_int_distribution<uint32_t>()(rng);
|
m_ssrc = std::uniform_int_distribution<uint32_t>()(rng);
|
||||||
@@ -96,7 +106,7 @@ namespace snoop
|
|||||||
std::lock_guard lk(m_mtx);
|
std::lock_guard lk(m_mtx);
|
||||||
if (!m_started)
|
if (!m_started)
|
||||||
return;
|
return;
|
||||||
|
m_trackOpen = false;
|
||||||
m_track.reset();
|
m_track.reset();
|
||||||
m_pc.reset();
|
m_pc.reset();
|
||||||
|
|
||||||
@@ -111,7 +121,7 @@ namespace snoop
|
|||||||
void PushOpus(const unsigned char *opusData, size_t opusBytes, int pcmFramesPerChannel)
|
void PushOpus(const unsigned char *opusData, size_t opusBytes, int pcmFramesPerChannel)
|
||||||
{
|
{
|
||||||
std::lock_guard lk(m_mtx);
|
std::lock_guard lk(m_mtx);
|
||||||
if (!m_track || !m_started)
|
if (!m_track || !m_started || !m_trackOpen.load())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Build RTP header (12 bytes)
|
// Build RTP header (12 bytes)
|
||||||
@@ -152,6 +162,7 @@ namespace snoop
|
|||||||
std::optional<std::string> m_resourceUrl;
|
std::optional<std::string> m_resourceUrl;
|
||||||
std::mutex m_mtx;
|
std::mutex m_mtx;
|
||||||
std::atomic<bool> m_started{false};
|
std::atomic<bool> m_started{false};
|
||||||
|
std::atomic<bool> m_trackOpen{false};
|
||||||
|
|
||||||
// RTP state
|
// RTP state
|
||||||
uint32_t m_ssrc = 0;
|
uint32_t m_ssrc = 0;
|
||||||
@@ -214,8 +225,13 @@ namespace snoop
|
|||||||
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
throw std::runtime_error("https URL but CPPHTTPLIB_OPENSSL_SUPPORT not enabled");
|
throw std::runtime_error("https URL but CPPHTTPLIB_OPENSSL_SUPPORT not enabled");
|
||||||
#else
|
#else
|
||||||
auto cli = std::make_unique<httplib::SSLClient>(host.c_str(), port, m_p.crtPath, m_p.keyPath, std::string());
|
auto payload = snoop::device_sec::ReadClientKeyPayloadFromKeyring();
|
||||||
cli->enable_server_certificate_verification(true);
|
// 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());
|
||||||
|
|
||||||
|
auto cli = std::make_unique<httplib::SSLClient>(host.c_str(), port, m_p.crtPath, tf.path, std::string());
|
||||||
|
cli->enable_server_certificate_verification(false);
|
||||||
cli->set_ca_cert_path(m_p.caPath.c_str());
|
cli->set_ca_cert_path(m_p.caPath.c_str());
|
||||||
cli->set_connection_timeout(10);
|
cli->set_connection_timeout(10);
|
||||||
cli->set_read_timeout(60);
|
cli->set_read_timeout(60);
|
||||||
|
|||||||
Reference in New Issue
Block a user