// formsrv.c - Remote forms server library implementation #define _POSIX_C_SOURCE 200809L #include "formsrv.h" #include #include #include #include // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- #define MAX_FORMS 64 #define MAX_LINES 1024 #define MAX_MSG_LEN 4096 // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- typedef struct { int32_t formId; char *lines[MAX_LINES]; int32_t lineCount; } FormDataT; struct FormServerS { FormTransportT *transport; EventCallbackT eventCallback; void *eventUserData; FormDataT forms[MAX_FORMS]; int32_t formCount; char msgBuf[MAX_MSG_LEN]; }; // --------------------------------------------------------------------------- // Prototypes // --------------------------------------------------------------------------- static FormDataT *findForm(FormServerT *server, int32_t formId); static void freeFormData(FormDataT *fd); static int32_t parseFormId(const char *line); static void sendCommand(FormServerT *server, const char *fmt, ...); static bool skipSpaces(const char **p); static bool parseToken(const char **p, char *buf, int32_t bufSize); // --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- static FormDataT *findForm(FormServerT *server, int32_t formId) { for (int32_t i = 0; i < server->formCount; i++) { if (server->forms[i].formId == formId) { return &server->forms[i]; } } return NULL; } static void freeFormData(FormDataT *fd) { for (int32_t i = 0; i < fd->lineCount; i++) { free(fd->lines[i]); fd->lines[i] = NULL; } fd->lineCount = 0; fd->formId = 0; } static int32_t parseFormId(const char *line) { // Skip command prefix (e.g., "FORM.CREATE "), then read first integer const char *p = line; // Skip non-space command name while (*p && *p != ' ') { p++; } // Skip space while (*p == ' ') { p++; } // Parse integer return (int32_t)atoi(p); } static bool skipSpaces(const char **p) { while (**p == ' ' || **p == '\t') { (*p)++; } return **p != '\0'; } 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; } #include 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); } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- FormServerT *formServerCreate(FormTransportT *transport) { FormServerT *server = (FormServerT *)calloc(1, sizeof(FormServerT)); if (server == NULL) { return NULL; } server->transport = transport; return server; } void formServerDestroy(FormServerT *server) { if (server == NULL) { return; } for (int32_t i = 0; i < server->formCount; i++) { freeFormData(&server->forms[i]); } free(server); } int32_t formServerLoadFile(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; } if (server->formCount >= MAX_FORMS) { fprintf(stderr, "formsrv: too many forms (max %d)\n", MAX_FORMS); fclose(f); return -1; } FormDataT *fd = &server->forms[server->formCount]; memset(fd, 0, sizeof(FormDataT)); char lineBuf[MAX_MSG_LEN]; int32_t formId = -1; 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; } if (fd->lineCount >= MAX_LINES) { fprintf(stderr, "formsrv: too many lines in '%s' (max %d)\n", path, MAX_LINES); freeFormData(fd); fclose(f); return -1; } fd->lines[fd->lineCount] = strdup(lineBuf); if (fd->lines[fd->lineCount] == NULL) { fprintf(stderr, "formsrv: out of memory\n"); freeFormData(fd); fclose(f); return -1; } fd->lineCount++; // Extract form ID from first FORM.CREATE line if (formId == -1 && strncmp(lineBuf, "FORM.CREATE ", 12) == 0) { formId = parseFormId(lineBuf); } } fclose(f); if (formId <= 0) { fprintf(stderr, "formsrv: no FORM.CREATE found in '%s'\n", path); freeFormData(fd); return -1; } fd->formId = formId; server->formCount++; return formId; } void formServerSendForm(FormServerT *server, int32_t formId) { FormDataT *fd = findForm(server, formId); if (fd == NULL) { return; } for (int32_t i = 0; i < fd->lineCount; i++) { server->transport->writeMessage(fd->lines[i], server->transport->ctx); } } void formServerShowForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.SHOW %d", formId); } void formServerHideForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.HIDE %d", formId); } void formServerDestroyForm(FormServerT *server, int32_t formId) { sendCommand(server, "FORM.DESTROY %d", formId); // Remove from form store for (int32_t i = 0; i < server->formCount; i++) { if (server->forms[i].formId == formId) { freeFormData(&server->forms[i]); // Shift remaining forms down for (int32_t j = i; j < server->formCount - 1; j++) { server->forms[j] = server->forms[j + 1]; } server->formCount--; break; } } } 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 formServerBindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) { sendCommand(server, "EVENT.BIND %d %d %s", formId, ctrlId, eventName); } void formServerUnbindEvent(FormServerT *server, int32_t formId, int32_t ctrlId, const char *eventName) { sendCommand(server, "EVENT.UNBIND %d %d %s", formId, ctrlId, eventName); } void formServerSetEventCallback(FormServerT *server, EventCallbackT cb, void *userData) { server->eventCallback = cb; server->eventUserData = userData; } 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; }