#include "proto.h" #include "umbrella.h" #include "log.h" #include #include #include #include #include #include /* ── 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; } }