diff options
Diffstat (limited to 'src/proto.c')
| -rw-r--r-- | src/proto.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/src/proto.c b/src/proto.c new file mode 100644 index 0000000..6c608d3 --- /dev/null +++ b/src/proto.c @@ -0,0 +1,183 @@ +#include "proto.h" +#include "umbrella.h" +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <arpa/inet.h> + +/* ── Low-level I/O ───────────────────────────────────────────────────────── */ + +/* + * write_all: keep writing until all bytes are sent or an error occurs. + * Returns 0 on success, -1 on error. + */ +static int write_all(int fd, const void *buf, size_t len) { + const uint8_t *p = buf; + while (len > 0) { + ssize_t n = write(fd, p, len); + if (n < 0) { + if (errno == EINTR) continue; + return -1; + } + p += n; + len -= n; + } + return 0; +} + +/* + * read_all: keep reading until exactly len bytes are received or error. + * Returns 0 on success, -1 on error, 1 on clean EOF. + */ +static int read_all(int fd, void *buf, size_t len) { + uint8_t *p = buf; + while (len > 0) { + ssize_t n = read(fd, p, len); + if (n < 0) { + if (errno == EINTR) continue; + return -1; + } + if (n == 0) + return 1; /* clean disconnect */ + p += n; + len -= n; + } + return 0; +} + +/* ── Protocol ────────────────────────────────────────────────────────────── */ + +int proto_send(int fd, const char *json) { + uint32_t len = (uint32_t)strlen(json); + uint32_t net_len = htonl(len); /* big-endian on the wire */ + + if (write_all(fd, &net_len, 4) != 0) { + log_debug("proto_send: write header failed: %s", strerror(errno)); + return -1; + } + if (write_all(fd, json, len) != 0) { + log_debug("proto_send: write body failed: %s", strerror(errno)); + return -1; + } + return 0; +} + +int proto_recv(int fd, char *buf, int buf_size) { + uint32_t net_len; + int r = read_all(fd, &net_len, 4); + if (r != 0) return r == 1 ? 0 : -1; /* 0=disconnect, -1=error */ + + uint32_t len = ntohl(net_len); + if (len == 0 || (int)len >= buf_size) { + log_warn("proto_recv: bad message length %u", len); + return -1; + } + + r = read_all(fd, buf, len); + if (r != 0) return r == 1 ? 0 : -1; + + buf[len] = '\0'; + return (int)len; +} + +/* ── Convenience senders ─────────────────────────────────────────────────── */ + +int proto_send_ok(int fd) { + return proto_send(fd, "{\"type\":\"ok\"}"); +} + +int proto_send_error(int fd, const char *message) { + char buf[512]; + /* Simple JSON escaping: replace " with \" */ + char escaped[256]; + int j = 0; + for (int i = 0; message[i] && j < 250; i++) { + if (message[i] == '"') escaped[j++] = '\\'; + escaped[j++] = message[i]; + } + escaped[j] = '\0'; + + snprintf(buf, sizeof(buf), + "{\"type\":\"error\",\"message\":\"%s\"}", escaped); + return proto_send(fd, buf); +} + +int proto_send_output(int fd, const char *unit_name, + const char *data, int history) { + /* + * We build the JSON manually to avoid a dependency on a JSON library. + * The data field needs escaping — we handle the common cases. + */ + char escaped[RING_BUF_LINE_MAX * 2]; + int j = 0; + for (int i = 0; data[i] && j < (int)sizeof(escaped) - 2; i++) { + switch (data[i]) { + case '"': escaped[j++] = '\\'; escaped[j++] = '"'; break; + case '\\': escaped[j++] = '\\'; escaped[j++] = '\\'; break; + case '\n': escaped[j++] = '\\'; escaped[j++] = 'n'; break; + case '\r': escaped[j++] = '\\'; escaped[j++] = 'r'; break; + default: escaped[j++] = data[i]; break; + } + } + escaped[j] = '\0'; + + char buf[PROTO_MAX_MSG]; + snprintf(buf, sizeof(buf), + "{\"type\":\"output\",\"unit\":\"%s\",\"data\":\"%s\"," + "\"history\":%s}", + unit_name, escaped, history ? "true" : "false"); + + return proto_send(fd, buf); +} + +/* ── Minimal JSON value extractor ────────────────────────────────────────── */ +/* + * Finds "key":"value" or "key":value in a flat JSON object. + * Not a full JSON parser — sufficient for our simple command messages. + */ +int json_get_str(const char *json, const char *key, + char *out, int out_size) { + char needle[MAX_NAME + 4]; + snprintf(needle, sizeof(needle), "\"%s\":", key); + + const char *p = strstr(json, needle); + if (!p) return 0; + + p += strlen(needle); + + /* Skip whitespace */ + while (*p == ' ' || *p == '\t') p++; + + if (*p == '"') { + /* Quoted string */ + p++; + int i = 0; + while (*p && *p != '"' && i < out_size - 1) { + if (*p == '\\' && *(p+1)) { + p++; + switch (*p) { + case 'n': out[i++] = '\n'; break; + case 'r': out[i++] = '\r'; break; + case 't': out[i++] = '\t'; break; + default: out[i++] = *p; break; + } + } else { + out[i++] = *p; + } + p++; + } + out[i] = '\0'; + return 1; + } else { + /* Unquoted value (number, bool) — read until delimiter */ + int i = 0; + while (*p && *p != ',' && *p != '}' && *p != ' ' && i < out_size - 1) + out[i++] = *p++; + out[i] = '\0'; + return i > 0 ? 1 : 0; + } +} |
