Private
Public Access
Release 0.13.0
This commit is contained in:
1 parent
7f6eeab39e
commit
c0d891b372
49 files changed
+1523
-478
No files matched your search
@@ -0,0 +1,263 @@
|
||||
#ifndef HYPERDRM_RECEIVER_OUTPUT_H
|
||||
#define HYPERDRM_RECEIVER_OUTPUT_H
|
||||
/* Main output connection. Every socket operation is nonblocking, with bounded
|
||||
* work per tick. One acknowledged command is in flight; only the newest unsent
|
||||
* color frame is retained. No Luna message or LG capture handle is owned here. */
|
||||
typedef enum {
|
||||
OUTPUT_IDLE,
|
||||
OUTPUT_CONNECTING,
|
||||
OUTPUT_LOGIN,
|
||||
OUTPUT_LAYOUT,
|
||||
OUTPUT_READY,
|
||||
OUTPUT_FAILED
|
||||
} OutputStage;
|
||||
typedef struct {
|
||||
int fd;
|
||||
OutputStage stage;
|
||||
TargetConfig target;
|
||||
char *incoming, *request, *latest;
|
||||
size_t used, sent, length;
|
||||
uint64_t deadline;
|
||||
unsigned transaction, awaiting, count;
|
||||
LedBlend *mapping;
|
||||
unsigned long acknowledgements;
|
||||
char error[128];
|
||||
bool closing;
|
||||
} ReceiverOutput;
|
||||
static void output_close(ReceiverOutput *o) {
|
||||
if (o->fd >= 0)
|
||||
close(o->fd);
|
||||
free(o->incoming);
|
||||
free(o->request);
|
||||
free(o->latest);
|
||||
free(o->mapping);
|
||||
*o = (ReceiverOutput){.fd = -1};
|
||||
}
|
||||
static void output_fail(ReceiverOutput *o, const char *error) {
|
||||
output_close(o);
|
||||
o->stage = OUTPUT_FAILED;
|
||||
snprintf(o->error, sizeof(o->error), "%s", error);
|
||||
}
|
||||
static bool output_request(ReceiverOutput *o, const char *line, uint64_t now) {
|
||||
size_t n = strlen(line);
|
||||
while (n && (line[n - 1] == '\n' || line[n - 1] == ' '))
|
||||
n--;
|
||||
if (!n || line[n - 1] != '}' || o->request || o->awaiting)
|
||||
return false;
|
||||
o->request = malloc(n + 48);
|
||||
if (!o->request)
|
||||
return false;
|
||||
if (o->transaction >= 2147483647u)
|
||||
o->transaction = 0;
|
||||
o->transaction++;
|
||||
memcpy(o->request, line, n - 1);
|
||||
snprintf(o->request + n - 1, 48, ",\"tan\":%u}\n", o->transaction);
|
||||
o->length = strlen(o->request);
|
||||
o->sent = 0;
|
||||
o->awaiting = o->transaction;
|
||||
o->deadline = now + 3000;
|
||||
return true;
|
||||
}
|
||||
static bool output_begin(ReceiverOutput *o, const TargetConfig *target, uint64_t now) {
|
||||
output_close(o);
|
||||
o->target = *target;
|
||||
o->incoming = malloc(2 * 1024 * 1024 + 1);
|
||||
if (!o->incoming) {
|
||||
output_fail(o, "Cannot allocate receiver buffer");
|
||||
return false;
|
||||
}
|
||||
o->fd = target_open(target);
|
||||
if (o->fd < 0) {
|
||||
output_fail(o, "Cannot open receiver connection");
|
||||
return false;
|
||||
}
|
||||
o->stage = OUTPUT_CONNECTING;
|
||||
o->deadline = now + 3000;
|
||||
return true;
|
||||
}
|
||||
static bool output_frame(ReceiverOutput *o, const char *line) {
|
||||
if (o->stage != OUTPUT_READY || o->closing)
|
||||
return false;
|
||||
if (strlen(line) > 2 * 1024 * 1024)
|
||||
return false;
|
||||
char *next = strdup(line);
|
||||
if (!next)
|
||||
return false;
|
||||
free(o->latest);
|
||||
o->latest = next;
|
||||
return true;
|
||||
}
|
||||
static void output_stop(ReceiverOutput *o, uint64_t now, bool clear) {
|
||||
if (!clear || o->stage != OUTPUT_READY) {
|
||||
output_close(o);
|
||||
return;
|
||||
}
|
||||
free(o->latest);
|
||||
o->latest = strdup("{\"command\":\"clear\",\"priority\":149}\n");
|
||||
if (!o->latest) {
|
||||
output_close(o);
|
||||
return;
|
||||
}
|
||||
o->closing = true;
|
||||
/* Allow the current transaction and the clear to finish asynchronously. */
|
||||
if (!o->awaiting)
|
||||
o->deadline = now + 3000;
|
||||
}
|
||||
static bool output_flush(ReceiverOutput *o, size_t *budget) {
|
||||
if (!o->request || !*budget)
|
||||
return true;
|
||||
size_t amount = o->length - o->sent;
|
||||
if (amount > *budget)
|
||||
amount = *budget;
|
||||
ssize_t n = send(o->fd, o->request + o->sent, amount, MSG_NOSIGNAL);
|
||||
if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
|
||||
output_fail(o, "Receiver closed during send");
|
||||
return false;
|
||||
}
|
||||
if (n > 0) {
|
||||
o->sent += (size_t)n;
|
||||
*budget -= (size_t)n;
|
||||
}
|
||||
if (o->sent == o->length) {
|
||||
free(o->request);
|
||||
o->request = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void output_tick(ReceiverOutput *o, PlaybackJson *json, uint64_t now) {
|
||||
if (o->stage == OUTPUT_IDLE || o->stage == OUTPUT_FAILED)
|
||||
return;
|
||||
if ((o->stage != OUTPUT_READY || o->awaiting || o->closing) && now >= o->deadline) {
|
||||
if (o->closing)
|
||||
output_close(o);
|
||||
else
|
||||
output_fail(o, "Receiver transaction timed out");
|
||||
return;
|
||||
}
|
||||
if (o->stage == OUTPUT_CONNECTING) {
|
||||
struct pollfd p = {o->fd, POLLOUT, 0};
|
||||
int error = 0;
|
||||
socklen_t size = sizeof(error);
|
||||
if (poll(&p, 1, 0) <= 0)
|
||||
return;
|
||||
if (getsockopt(o->fd, SOL_SOCKET, SO_ERROR, &error, &size) || error) {
|
||||
output_fail(o, "Receiver connection refused or unreachable");
|
||||
return;
|
||||
}
|
||||
char line[384];
|
||||
if (o->target.token[0]) {
|
||||
snprintf(line, sizeof(line),
|
||||
"{\"command\":\"authorize\",\"subcommand\":\"login\",\"token\":\"%s\"}\n",
|
||||
o->target.token);
|
||||
o->stage = OUTPUT_LOGIN;
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "{\"command\":\"serverinfo\"}\n");
|
||||
o->stage = OUTPUT_LAYOUT;
|
||||
}
|
||||
if (!output_request(o, line, now)) {
|
||||
output_fail(o, "Cannot allocate receiver request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (o->stage == OUTPUT_READY && !o->awaiting && o->latest) {
|
||||
if (!output_request(o, o->latest, now)) {
|
||||
output_fail(o, "Cannot allocate color request");
|
||||
return;
|
||||
}
|
||||
free(o->latest);
|
||||
o->latest = NULL;
|
||||
}
|
||||
size_t send_budget = 65536;
|
||||
if (!output_flush(o, &send_budget))
|
||||
return;
|
||||
size_t space = 2 * 1024 * 1024 - o->used;
|
||||
if (space > 65536)
|
||||
space = 65536;
|
||||
if (!space && !memchr(o->incoming, '\n', o->used)) {
|
||||
output_fail(o, "Receiver response exceeds 2 MiB");
|
||||
return;
|
||||
}
|
||||
ssize_t n = space ? recv(o->fd, o->incoming + o->used, space, 0) : -1;
|
||||
if (!space)
|
||||
errno = EAGAIN;
|
||||
if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
|
||||
output_fail(o, "Receiver closed during receive");
|
||||
return;
|
||||
}
|
||||
if (n > 0) {
|
||||
if (memchr(o->incoming + o->used, 0, (size_t)n)) {
|
||||
output_fail(o, "Receiver sent an embedded NUL");
|
||||
return;
|
||||
}
|
||||
o->used += (size_t)n;
|
||||
}
|
||||
o->incoming[o->used] = 0;
|
||||
for (unsigned budget = 0; budget < 8; budget++) {
|
||||
char *end = memchr(o->incoming, '\n', o->used);
|
||||
if (!end)
|
||||
break;
|
||||
*end = 0;
|
||||
void *root = playback_parse_exact(json, o->incoming);
|
||||
if (!root || json->type(root) != 4) {
|
||||
if (root)
|
||||
json->put(root);
|
||||
output_fail(o, "Receiver returned malformed JSON");
|
||||
return;
|
||||
}
|
||||
void *tan = playback_field(json, root, "tan");
|
||||
char *tail = NULL;
|
||||
unsigned long id = tan && json->type(tan) == 3 ? strtoul(json->string(tan), &tail, 10) : 0;
|
||||
bool matches = o->awaiting && tail && !*tail && id == o->awaiting;
|
||||
if (matches) {
|
||||
if (o->request || !playback_true(json, root, "success")) {
|
||||
json->put(root);
|
||||
output_fail(o, o->request ? "Receiver acknowledged an incomplete request"
|
||||
: o->stage == OUTPUT_LOGIN
|
||||
? "Receiver authentication failed; check API token"
|
||||
: o->stage == OUTPUT_LAYOUT ? "Receiver rejected LED layout request"
|
||||
: "Receiver rejected color request");
|
||||
return;
|
||||
}
|
||||
o->awaiting = 0;
|
||||
if (o->stage == OUTPUT_LOGIN) {
|
||||
o->stage = OUTPUT_LAYOUT;
|
||||
if (!output_request(o, "{\"command\":\"serverinfo\"}\n", now)) {
|
||||
json->put(root);
|
||||
output_fail(o, "Cannot allocate layout request");
|
||||
return;
|
||||
}
|
||||
} else if (o->stage == OUTPUT_LAYOUT) {
|
||||
if (!layout_from_json(o->incoming, &o->mapping, &o->count)) {
|
||||
json->put(root);
|
||||
output_fail(o, "Receiver returned an invalid LED layout");
|
||||
return;
|
||||
}
|
||||
o->stage = OUTPUT_READY;
|
||||
} else {
|
||||
o->acknowledgements++;
|
||||
if (o->closing && !o->latest) {
|
||||
json->put(root);
|
||||
output_close(o);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
json->put(root);
|
||||
size_t consumed = (size_t)(end - o->incoming) + 1;
|
||||
memmove(o->incoming, o->incoming + consumed, o->used - consumed);
|
||||
o->used -= consumed;
|
||||
o->incoming[o->used] = 0;
|
||||
}
|
||||
/* An ACK can free the slot after the first send pass. Use the remaining
|
||||
* byte budget to send the newest queued frame in this same tick. */
|
||||
if (o->stage == OUTPUT_READY && !o->awaiting && o->latest) {
|
||||
if (!output_request(o, o->latest, now)) {
|
||||
output_fail(o, "Cannot allocate color request");
|
||||
return;
|
||||
}
|
||||
free(o->latest);
|
||||
o->latest = NULL;
|
||||
}
|
||||
output_flush(o, &send_budget);
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user