2022-01-21 19:22:13 +00:00
|
|
|
#include "sdrpp_server_client.h"
|
|
|
|
#include <volk/volk.h>
|
|
|
|
#include <cstring>
|
2023-02-25 17:12:34 +00:00
|
|
|
#include <utils/flog.h>
|
2022-01-21 19:22:13 +00:00
|
|
|
#include <core.h>
|
|
|
|
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
|
|
|
namespace server {
|
2024-01-28 20:46:54 +00:00
|
|
|
Client::Client(std::shared_ptr<net::Socket> sock, dsp::stream<dsp::complex_t>* out) {
|
|
|
|
this->sock = sock;
|
2022-01-21 19:22:13 +00:00
|
|
|
output = out;
|
|
|
|
|
|
|
|
// Allocate buffers
|
|
|
|
rbuffer = new uint8_t[SERVER_MAX_PACKET_SIZE];
|
|
|
|
sbuffer = new uint8_t[SERVER_MAX_PACKET_SIZE];
|
|
|
|
|
|
|
|
// Initialize headers
|
|
|
|
r_pkt_hdr = (PacketHeader*)rbuffer;
|
|
|
|
r_pkt_data = &rbuffer[sizeof(PacketHeader)];
|
|
|
|
r_cmd_hdr = (CommandHeader*)r_pkt_data;
|
|
|
|
r_cmd_data = &rbuffer[sizeof(PacketHeader) + sizeof(CommandHeader)];
|
|
|
|
|
|
|
|
s_pkt_hdr = (PacketHeader*)sbuffer;
|
|
|
|
s_pkt_data = &sbuffer[sizeof(PacketHeader)];
|
|
|
|
s_cmd_hdr = (CommandHeader*)s_pkt_data;
|
|
|
|
s_cmd_data = &sbuffer[sizeof(PacketHeader) + sizeof(CommandHeader)];
|
|
|
|
|
2022-01-26 12:23:55 +00:00
|
|
|
// Initialize decompressor
|
|
|
|
dctx = ZSTD_createDCtx();
|
|
|
|
|
2022-01-21 19:22:13 +00:00
|
|
|
// Initialize DSP
|
2024-02-13 17:39:11 +00:00
|
|
|
decompIn.setBufferSize(STREAM_BUFFER_SIZE*sizeof(dsp::complex_t) + 8);
|
2022-01-29 16:42:53 +00:00
|
|
|
decompIn.clearWriteStop();
|
2022-01-21 19:22:13 +00:00
|
|
|
decomp.init(&decompIn);
|
|
|
|
link.init(&decomp.out, output);
|
|
|
|
decomp.start();
|
|
|
|
link.start();
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
// Start worker thread
|
|
|
|
workerThread = std::thread(&Client::worker, this);
|
2022-01-21 19:22:13 +00:00
|
|
|
|
|
|
|
// Ask for a UI
|
2022-01-22 01:30:08 +00:00
|
|
|
int res = getUI();
|
|
|
|
if (res == -1) { throw std::runtime_error("Timed out"); }
|
|
|
|
else if (res == -2) { throw std::runtime_error("Server busy"); }
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
Client::~Client() {
|
2022-01-21 19:22:13 +00:00
|
|
|
close();
|
2022-01-26 12:23:55 +00:00
|
|
|
ZSTD_freeDCtx(dctx);
|
2022-01-21 19:22:13 +00:00
|
|
|
delete[] rbuffer;
|
|
|
|
delete[] sbuffer;
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::showMenu() {
|
2022-01-21 19:22:13 +00:00
|
|
|
std::string diffId = "";
|
|
|
|
SmGui::DrawListElem diffValue;
|
|
|
|
bool syncRequired = false;
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lck(dlMtx);
|
|
|
|
dl.draw(diffId, diffValue, syncRequired);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!diffId.empty()) {
|
|
|
|
// Save ID
|
|
|
|
SmGui::DrawListElem elemId;
|
|
|
|
elemId.type = SmGui::DRAW_LIST_ELEM_TYPE_STRING;
|
|
|
|
elemId.str = diffId;
|
|
|
|
|
|
|
|
// Encore packet
|
|
|
|
int size = 0;
|
|
|
|
s_cmd_data[size++] = syncRequired;
|
|
|
|
size += SmGui::DrawList::storeItem(elemId, &s_cmd_data[size], SERVER_MAX_PACKET_SIZE - size);
|
|
|
|
size += SmGui::DrawList::storeItem(diffValue, &s_cmd_data[size], SERVER_MAX_PACKET_SIZE - size);
|
|
|
|
|
|
|
|
// Send
|
|
|
|
if (syncRequired) {
|
2023-02-25 17:12:34 +00:00
|
|
|
flog::warn("Action requires resync");
|
2022-01-21 19:22:13 +00:00
|
|
|
auto waiter = awaitCommandAck(COMMAND_UI_ACTION);
|
|
|
|
sendCommand(COMMAND_UI_ACTION, size);
|
|
|
|
if (waiter->await(PROTOCOL_TIMEOUT_MS)) {
|
|
|
|
std::lock_guard lck(dlMtx);
|
|
|
|
dl.load(r_cmd_data, r_pkt_hdr->size - sizeof(PacketHeader) - sizeof(CommandHeader));
|
|
|
|
}
|
|
|
|
else {
|
2023-02-25 17:12:34 +00:00
|
|
|
flog::error("Timeout out after asking for UI");
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
waiter->handled();
|
2023-02-25 17:12:34 +00:00
|
|
|
flog::warn("Resync done");
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
else {
|
2023-02-25 17:12:34 +00:00
|
|
|
flog::warn("Action does not require resync");
|
2022-01-21 19:22:13 +00:00
|
|
|
sendCommand(COMMAND_UI_ACTION, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::setFrequency(double freq) {
|
|
|
|
if (!isOpen()) { return; }
|
2022-01-21 19:22:13 +00:00
|
|
|
*(double*)s_cmd_data = freq;
|
|
|
|
sendCommand(COMMAND_SET_FREQUENCY, sizeof(double));
|
|
|
|
auto waiter = awaitCommandAck(COMMAND_SET_FREQUENCY);
|
|
|
|
waiter->await(PROTOCOL_TIMEOUT_MS);
|
|
|
|
waiter->handled();
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
double Client::getSampleRate() {
|
2022-01-21 19:22:13 +00:00
|
|
|
return currentSampleRate;
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::setSampleType(dsp::compression::PCMType type) {
|
|
|
|
if (!isOpen()) { return; }
|
2022-01-21 19:22:13 +00:00
|
|
|
s_cmd_data[0] = type;
|
|
|
|
sendCommand(COMMAND_SET_SAMPLE_TYPE, 1);
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::setCompression(bool enabled) {
|
|
|
|
if (!isOpen()) { return; }
|
2022-01-26 12:23:55 +00:00
|
|
|
s_cmd_data[0] = enabled;
|
|
|
|
sendCommand(COMMAND_SET_COMPRESSION, 1);
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::start() {
|
|
|
|
if (!isOpen()) { return; }
|
2022-01-21 19:22:13 +00:00
|
|
|
sendCommand(COMMAND_START, 0);
|
|
|
|
getUI();
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::stop() {
|
|
|
|
if (!isOpen()) { return; }
|
2022-01-21 19:22:13 +00:00
|
|
|
sendCommand(COMMAND_STOP, 0);
|
|
|
|
getUI();
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::close() {
|
|
|
|
// Stop worker
|
2022-01-29 16:42:53 +00:00
|
|
|
decompIn.stopWriter();
|
2024-01-28 20:46:54 +00:00
|
|
|
if (sock) { sock->close(); }
|
|
|
|
if (workerThread.joinable()) { workerThread.join(); }
|
2022-01-29 16:42:53 +00:00
|
|
|
decompIn.clearWriteStop();
|
2024-01-28 20:46:54 +00:00
|
|
|
|
|
|
|
// Stop DSP
|
|
|
|
decomp.stop();
|
|
|
|
link.stop();
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
bool Client::isOpen() {
|
|
|
|
return sock && sock->isOpen();
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::worker() {
|
|
|
|
while (true) {
|
|
|
|
// Receive header
|
|
|
|
if (sock->recv(rbuffer, sizeof(PacketHeader), true) <= 0) {
|
|
|
|
break;
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
2022-01-22 01:30:08 +00:00
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
// Receive remaining data
|
|
|
|
if (sock->recv(&rbuffer[sizeof(PacketHeader)], r_pkt_hdr->size - sizeof(PacketHeader), true, PROTOCOL_TIMEOUT_MS) <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increment data counter
|
|
|
|
bytes += r_pkt_hdr->size;
|
|
|
|
|
|
|
|
// Decode packet
|
|
|
|
if (r_pkt_hdr->type == PACKET_TYPE_COMMAND) {
|
|
|
|
// TODO: Move to command handler
|
|
|
|
if (r_cmd_hdr->cmd == COMMAND_SET_SAMPLERATE && r_pkt_hdr->size == sizeof(PacketHeader) + sizeof(CommandHeader) + sizeof(double)) {
|
|
|
|
currentSampleRate = *(double*)r_cmd_data;
|
|
|
|
core::setInputSampleRate(currentSampleRate);
|
|
|
|
}
|
|
|
|
else if (r_cmd_hdr->cmd == COMMAND_DISCONNECT) {
|
|
|
|
flog::error("Asked to disconnect by the server");
|
|
|
|
serverBusy = true;
|
|
|
|
|
|
|
|
// Cancel waiters
|
|
|
|
std::vector<PacketWaiter*> toBeRemoved;
|
|
|
|
for (auto& [waiter, cmd] : commandAckWaiters) {
|
|
|
|
waiter->cancel();
|
|
|
|
toBeRemoved.push_back(waiter);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove handled waiters
|
|
|
|
for (auto& waiter : toBeRemoved) {
|
|
|
|
commandAckWaiters.erase(waiter);
|
|
|
|
delete waiter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (r_pkt_hdr->type == PACKET_TYPE_COMMAND_ACK) {
|
|
|
|
// Notify waiters
|
2022-01-22 01:30:08 +00:00
|
|
|
std::vector<PacketWaiter*> toBeRemoved;
|
2024-01-28 20:46:54 +00:00
|
|
|
for (auto& [waiter, cmd] : commandAckWaiters) {
|
|
|
|
if (cmd != r_cmd_hdr->cmd) { continue; }
|
|
|
|
waiter->notify();
|
2022-01-22 01:30:08 +00:00
|
|
|
toBeRemoved.push_back(waiter);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove handled waiters
|
|
|
|
for (auto& waiter : toBeRemoved) {
|
2024-01-28 20:46:54 +00:00
|
|
|
commandAckWaiters.erase(waiter);
|
2022-01-22 01:30:08 +00:00
|
|
|
delete waiter;
|
|
|
|
}
|
|
|
|
}
|
2024-01-28 20:46:54 +00:00
|
|
|
else if (r_pkt_hdr->type == PACKET_TYPE_BASEBAND) {
|
|
|
|
memcpy(decompIn.writeBuf, &rbuffer[sizeof(PacketHeader)], r_pkt_hdr->size - sizeof(PacketHeader));
|
|
|
|
if (!decompIn.swap(r_pkt_hdr->size - sizeof(PacketHeader))) { break; }
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
2024-01-28 20:46:54 +00:00
|
|
|
else if (r_pkt_hdr->type == PACKET_TYPE_BASEBAND_COMPRESSED) {
|
2024-02-13 17:39:11 +00:00
|
|
|
size_t outCount = ZSTD_decompressDCtx(dctx, decompIn.writeBuf, STREAM_BUFFER_SIZE*sizeof(dsp::complex_t)+8, r_pkt_data, r_pkt_hdr->size - sizeof(PacketHeader));
|
2024-01-28 20:46:54 +00:00
|
|
|
if (outCount) {
|
|
|
|
if (!decompIn.swap(outCount)) { break; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (r_pkt_hdr->type == PACKET_TYPE_ERROR) {
|
|
|
|
flog::error("SDR++ Server Error: {0}", rbuffer[sizeof(PacketHeader)]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
flog::error("Invalid packet type: {0}", r_pkt_hdr->type);
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
int Client::getUI() {
|
|
|
|
if (!isOpen()) { return -1; }
|
2022-01-21 19:22:13 +00:00
|
|
|
auto waiter = awaitCommandAck(COMMAND_GET_UI);
|
|
|
|
sendCommand(COMMAND_GET_UI, 0);
|
|
|
|
if (waiter->await(PROTOCOL_TIMEOUT_MS)) {
|
|
|
|
std::lock_guard lck(dlMtx);
|
|
|
|
dl.load(r_cmd_data, r_pkt_hdr->size - sizeof(PacketHeader) - sizeof(CommandHeader));
|
|
|
|
}
|
|
|
|
else {
|
2023-02-25 17:12:34 +00:00
|
|
|
if (!serverBusy) { flog::error("Timeout out after asking for UI"); };
|
2022-01-22 01:30:08 +00:00
|
|
|
waiter->handled();
|
|
|
|
return serverBusy ? -2 : -1;
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
waiter->handled();
|
2022-01-22 01:30:08 +00:00
|
|
|
return 0;
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::sendPacket(PacketType type, int len) {
|
2022-01-21 19:22:13 +00:00
|
|
|
s_pkt_hdr->type = type;
|
|
|
|
s_pkt_hdr->size = sizeof(PacketHeader) + len;
|
2024-01-28 20:46:54 +00:00
|
|
|
sock->send(sbuffer, s_pkt_hdr->size);
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::sendCommand(Command cmd, int len) {
|
2022-01-21 19:22:13 +00:00
|
|
|
s_cmd_hdr->cmd = cmd;
|
|
|
|
sendPacket(PACKET_TYPE_COMMAND, sizeof(CommandHeader) + len);
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::sendCommandAck(Command cmd, int len) {
|
2022-01-21 19:22:13 +00:00
|
|
|
s_cmd_hdr->cmd = cmd;
|
|
|
|
sendPacket(PACKET_TYPE_COMMAND_ACK, sizeof(CommandHeader) + len);
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
PacketWaiter* Client::awaitCommandAck(Command cmd) {
|
2022-01-21 19:22:13 +00:00
|
|
|
PacketWaiter* waiter = new PacketWaiter;
|
|
|
|
commandAckWaiters[waiter] = cmd;
|
|
|
|
return waiter;
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
void Client::dHandler(dsp::complex_t *data, int count, void *ctx) {
|
|
|
|
Client* _this = (Client*)ctx;
|
2022-01-21 19:22:13 +00:00
|
|
|
memcpy(_this->output->writeBuf, data, count * sizeof(dsp::complex_t));
|
|
|
|
_this->output->swap(count);
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:46:54 +00:00
|
|
|
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
|
|
|
|
return std::make_shared<Client>(net::connect(host, port), out);
|
2022-01-21 19:22:13 +00:00
|
|
|
}
|
|
|
|
}
|