// formsrv.c - Remote forms server library implementation #define _POSIX_C_SOURCE 200809L #include "formsrv.h" #include #include #include #include #include // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- #define MAX_MSG_LEN 4096 // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- struct FormServerS { FormTransportT *transport; EventCallbackT eventCallback; void *eventUserData; int32_t nextFormId; char msgBuf[MAX_MSG_LEN]; }; // --------------------------------------------------------------------------- // Prototypes // --------------------------------------------------------------------------- static bool parseToken(const char **p, char *buf, int32_t bufSize); static void rewriteFormId(char *line, int32_t lineSize, int32_t formId); static void sendCommand(FormServerT *server, const char *fmt, ...); static bool skipSpaces(const char **p); // --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- static bool parseToken(const char **p, char *buf, int32_t bufSize) { skipSpaces(p); if (**p == '\0') { return false; } int32_t i = 0; if (**p == '"') { // Quoted string — read until closing quote (*p)++; while (**p != '\0' && **p != '"') { if (**p == '\\' && *(*p + 1) != '\0') { (*p)++; char c = **p; switch (c) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '"': c = '"'; break; case '\\': c = '\\'; break; default: break; } if (i < bufSize - 1) { buf[i++] = c; } } else { if (i < bufSize - 1) { buf[i++] = **p; } } (*p)++; } if (**p == '"') { (*p)++; } } else { // Bare token — read until whitespace while (**p != '\0' && **p != ' ' && **p != '\t') { if (i < bufSize - 1) { buf[i++] = **p; } (*p)++; } } buf[i] = '\0'; return i > 0; } static void rewriteFormId(char *line, int32_t lineSize, int32_t formId) { // Protocol lines have the form: COMMAND // Skip command prefix (non-space chars) char *p = line; while (*p && *p != ' ') { p++; } // Skip spaces while (*p == ' ') { p++; } // p now points at the placeholder ID token ("0") // Find the end of this token char *idStart = p; while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') { p++; } char *idEnd = p; // Format the new ID char idBuf[16]; int32_t idLen = snprintf(idBuf, sizeof(idBuf), "%d", formId); int32_t oldLen = (int32_t)(idEnd - idStart); int32_t shift = idLen - oldLen; // Check that the rewritten line still fits int32_t curLen = (int32_t)strlen(line); if (curLen + shift >= lineSize - 1) { return; } // Shift the remainder of the line if (shift != 0) { memmove(idStart + idLen, idEnd, strlen(idEnd) + 1); } // Write the new ID (no NUL — the memmove already placed the rest) memcpy(idStart, idBuf, idLen); } static void sendCommand(FormServerT *server, const char *fmt, ...) { char buf[MAX_MSG_LEN]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); server->transport->writeMessage(buf, server->transport->ctx); } static bool skipSpaces(const char **p) { while (**p == ' ' || **p == '\t') { (*p)++; } return **p != '\0'; } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- void formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) { sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName); } FormServerT *formServerCreate(FormTransportT *transport) { FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT)); if (server == NULL) { return NULL; } server->transport = transport; server->nextFormId = 1; return server; } void formServerDestroy(FormServerT *server) { if (server == NULL) { return; } free(server); } void formServerDestroyForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.DESTROY %d", formId); } void formServerHideForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.HIDE %d", formId); } bool formServerPollEvent(FormServerT *server) { int bytesRead = server->transport->readMessage( server->msgBuf, MAX_MSG_LEN - 1, server->transport->ctx); if (bytesRead <= 0) { return false; } server->msgBuf[bytesRead] = '\0'; // Parse: EVENT [] const char *p = server->msgBuf; char token[256]; if (!parseToken(&p, token, sizeof(token))) { return false; } if (strcmp(token, "EVENT") != 0) { return false; } // formId if (!parseToken(&p, token, sizeof(token))) { return false; } int32_t formId = (int32_t)atoi(token); // ctrlId if (!parseToken(&p, token, sizeof(token))) { return false; } int32_t ctrlId = (int32_t)atoi(token); // eventName char eventName[64]; if (!parseToken(&p, eventName, sizeof(eventName))) { return false; } // Remaining data (rest of line after skipping spaces) skipSpaces(&p); const char *data = p; if (server->eventCallback != NULL) { server->eventCallback(formId, ctrlId, eventName, data, server->eventUserData); } return true; } int32_t formServerSendForm(FormServerT *server, const char *path) { FILE *f = fopen(path, "r"); if (f == NULL) { fprintf(stderr, "formsrv: cannot open '%s': %s\n", path, strerror(errno)); return -1; } int32_t formId = server->nextFormId++; char lineBuf[MAX_MSG_LEN]; while (fgets(lineBuf, sizeof(lineBuf), f) != NULL) { // Strip trailing newline int32_t len = (int32_t)strlen(lineBuf); while (len > 0 && (lineBuf[len - 1] == '\n' || lineBuf[len - 1] == '\r')) { lineBuf[--len] = '\0'; } // Skip empty lines if (len == 0) { continue; } rewriteFormId(lineBuf, sizeof(lineBuf), formId); server->transport->writeMessage(lineBuf, server->transport->ctx); } fclose(f); return formId; } void formServerSetEventCallback(FormServerT *server, EventCallbackT cb, void *userData) { server->eventCallback = cb; server->eventUserData = userData; } void formServerSetProp(FormServerT *server, int32_t formId, int32_t ctrlId, const char *prop, const char *value) { sendCommand(server, "CTRL.SET %d %d %s=%s", formId, ctrlId, prop, value); } void formServerShowForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.SHOW %d", formId); } void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) { sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName); }