diff --git a/Makefile b/Makefile index 5921c63..c3ba41d 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ CC := gcc # Enable for debug CFLAGS := -g -ggdb -std=c99 -Wall -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wstrict-prototypes -Werror -INCLUDES := -I. +INCLUDES := -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -siahsd_LIB := -ltalloc +siahsd_LIB := -ltalloc -ldbi -lglib-2.0 siahsd_OBJ := sia.o siahsd.o OBJ := $(siahsd_OBJ) diff --git a/siahsd.c b/siahsd.c index 636c457..ba48aea 100644 --- a/siahsd.c +++ b/siahsd.c @@ -6,23 +6,75 @@ #include #include #include + +/* Libs */ #include +#include +#include + +/* Private */ +#include "siahsd.h" #include "siahs.h" #include "sia.h" #define MY_DEVICE "RCIPv2.4" +#define CONFIGFILE "/etc/siahsd.conf" /* TODO: + * - Add debug logging + * - Better error handling (no void functions) + * - Add event connection to jsonbot + * - Keep PROM state and monitor keepalives * - Make a load balancer that balances REGISTRATION REQUESTS to the proper port - * - Actually do something with the messages: Add to database, keep state, etc */ -void parse_message(TALLOC_CTX *mem_ctx, struct packet *pkt) { + +/* + * talloc_quoted_string escapes quotes in a string and encapsulates it in quotes. + * It returns a pointer to talloc'ed memory, the quoted string. + */ +char *talloc_quoted_string(TALLOC_CTX *mem_ctx, const char *string) { + /* Allocate twice the string length, to be safe and not having to realloc all the time */ + char *ret = talloc_zero_array(mem_ctx, char, strlen(string) * 2 + 1); + size_t i, j; + + if (ret == NULL) return NULL; + + ret[0] = '\''; + + for (i = 0, j = 1; i < strlen(string); i++, j++) { + if (string[i] == '\'' || string[i] == '\\') { + ret[j] = '\''; + ret[++j] = string[i]; + } else { + ret[j] = string[i]; + } + } + ret[j] = '\''; + ret[++j] = '\0'; + + return ret; +} + +/* + * parse_message parses the string portion of the SIA-HS message + * and writes the event to the database. + * It returns nothing. + */ +void parse_message(TALLOC_CTX *mem_ctx, dbi_conn conn, struct packet *pkt) { char *message = talloc_strdup(mem_ctx, pkt->message + strlen("MESSAGE ")); char *ptr = message; char *prom = ptr; + char *pkt_prom; char *code; + char *quoted_prom; + char *quoted_code; + char *quoted_long_code; + char *quoted_description; + + /* FIXME: Handle out of memory situation better */ + if (message == NULL) return; /* Grab the first part, the prom */ while (*ptr != '\0' && *ptr != 'N') { @@ -37,19 +89,44 @@ void parse_message(TALLOC_CTX *mem_ctx, struct packet *pkt) { } if (*ptr != '\0') *ptr++ = '\0'; - /* The remaining ptr contains the information string */ + /* The remaining ptr contains the human readable description string */ /* Ignore alive! messages */ if (strcmp(code, "alive!") == 0) { + /* FIXME We must update some keepalive status somewhere to generate offline messages */ return; } + /* Assert that string prom is identical to hex representation of pkt->prom */ + pkt_prom = talloc_asprintf(message, "%04x", pkt->prom); + + /* FIXME: Handle out of memory situation better */ + if (pkt_prom == NULL) return; + + if (strcmp(pkt_prom, prom) != 0) { + /* FIXME: Error handling should be improved */ + return; + } + + quoted_prom = talloc_quoted_string(message, prom); + quoted_code = talloc_quoted_string(message, code); + quoted_long_code = talloc_quoted_string(message, sia_code_str(code)); + quoted_description = talloc_quoted_string(message, ptr); + printf("%s %s %s -- %s: %s\n", prom, code, ptr, sia_code_str(code), sia_code_desc(code)); + dbi_conn_queryf(conn, "INSERT INTO events (timestamp, prom, code, long_code, description) VALUES (NOW(), %s, %s, %s, %s)\n", + quoted_prom, quoted_code, quoted_long_code, quoted_description); + talloc_free(message); } - +/* + * send_reply sends a reply to a SIA-HS transmitter + * It requires a memory context, the socket from which to reply, the socket address to reply to, the original packet + * and a string with the reply message. + * It returns nothing. + */ void send_reply(TALLOC_CTX *mem_ctx, int sock, struct sockaddr_in from, struct packet *pkt, const char *string) { int n; uint8_t *reply; @@ -107,23 +184,80 @@ void send_reply(TALLOC_CTX *mem_ctx, int sock, struct sockaddr_in from, struct p talloc_free(reply); } + + int main(int argc, char **argv) { int sock, n, i; socklen_t fromlen; struct sockaddr_in server; struct sockaddr_in from; TALLOC_CTX *mem_ctx; + GKeyFile *keyfile; + GError *error = NULL; + configuration *conf; + dbi_conn conn; /* Initialize a memory context */ mem_ctx = talloc_init("siahsd"); /* - * Open up a UDP socket on port 4000 + * Read the configuration file + */ + keyfile = g_key_file_new (); + + if (!g_key_file_load_from_file (keyfile, CONFIGFILE, 0, &error)) { + g_error (error->message); + return -1; + } + + conf = talloc(mem_ctx, configuration); + if (conf == NULL) return -1; + conf->database_host = g_key_file_get_string(keyfile, "database", + "host", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + conf->database_name = g_key_file_get_string(keyfile, "database", + "name", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + conf->database_driver = g_key_file_get_string(keyfile, "database", + "driver", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + conf->database_username = g_key_file_get_string(keyfile, "database", + "username", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + conf->database_password = g_key_file_get_string(keyfile, "database", + "password", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + + conf->siahs_port = g_key_file_get_integer(keyfile, "siahs", "port", &error); + if (error) { + printf("No database host supplied in the configuration.\n"); + return -1; + } + + /* + * Open up a UDP socket the configured port */ sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) - printf("Can not create socket in server\n"); + if (sock < 0) { + printf("Could not create socket in server\n"); + return -1; + } memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; @@ -131,10 +265,28 @@ int main(int argc, char **argv) { server.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { - printf("Can not bind to socket!\n"); - exit(1); + printf("Could not bind to socket!\n"); + return -1; } + printf("Connecting to %s database %s at %s", conf->database_driver, + conf->database_name, conf->database_host); + + /* + * Open a connection to the database + */ + dbi_initialize(NULL); + conn = dbi_conn_new(conf->database_driver); + dbi_conn_set_option(conn, "host", conf->database_host); + dbi_conn_set_option(conn, "username", conf->database_username); + dbi_conn_set_option(conn, "password", conf->database_password); + dbi_conn_set_option(conn, "dbname", conf->database_name); + dbi_conn_set_option(conn, "encoding", "UTF-8"); + + if (dbi_conn_connect(conn) < 0) { + printf("Could not connect to the database\n"); + return -1; + } /* * Wait for packets @@ -145,9 +297,13 @@ int main(int argc, char **argv) { struct packet *pkt; uint8_t *decoded; char buf[1024]; /* Purposefully static length */ + char *reply_message; pkt = talloc_zero(mem_ctx, struct packet); + /* FIXME: Handle out of memory situation gracefully */ + if (pkt == NULL) return 1; + n = recvfrom(sock, &buf, sizeof(buf), 0, (struct sockaddr *) &from, &fromlen); if (n < 0) { printf("Error when receiving in server!\n"); @@ -175,6 +331,9 @@ int main(int argc, char **argv) { decoded = talloc_memdup(pkt, &buf[8], pkt->len - 6); + /* FIXME: Handle out of memory situation gracefully */ + if (decoded == NULL) return 1; + /* Decode with XOR 0xB6 */ for (i = 0;i < pkt->len - 6; i++) { @@ -183,6 +342,9 @@ int main(int argc, char **argv) { pkt->device = talloc_strndup(pkt, (char *)decoded, 12); + /* FIXME: Handle out of memory situation gracefully */ + if (pkt->device == NULL) return 1; + pkt->prom = ntohs(*(uint16_t *)&decoded[13]); pkt->unknown4 = decoded[16]; pkt->unknown5 = decoded[17]; @@ -190,21 +352,30 @@ int main(int argc, char **argv) { pkt->message = talloc_strndup(pkt, (char *) &decoded[26], pkt->len-32); + /* FIXME: Handle out of memory situation gracefully */ + if (pkt->message == NULL) return 1; + printf("I have received device %s prom %x, message %s, from IP %s and port %u \n", pkt->device, pkt->prom, pkt->message, inet_ntoa(from.sin_addr), src_port); /* Handle registrations, reconnects and messages */ if (strcmp(pkt->message, "REGISTRATION REQUEST") == 0) { - send_reply(pkt, sock, from, pkt, "REGISTRATION RENEWAL AT PORT 04000"); + /* XXX I'm sending this to this very same socket now. This should be used as a dispatcher */ + reply_message = talloc_asprintf(pkt, "REGISTRATION RENEWAL AT PORT %05d", conf->siahs_port); + if (reply_message == NULL) return -1; + send_reply(pkt, sock, from, pkt, reply_message); } else if (strcmp(pkt->message, "RECONNECT REQUEST") == 0) { - send_reply(pkt, sock, from, pkt, "RECONNECTED AT PORT 04000"); + /* XXX This is the first message that arrives at the registration referred port */ + reply_message = talloc_asprintf(pkt, "RECONNECTED AT PORT %05d", conf->siahs_port); + if (reply_message == NULL) return -1; + send_reply(pkt, sock, from, pkt, reply_message); } else if (strncmp(pkt->message, "MESSAGE ", strlen("MESSAGE ")) == 0) { send_reply(pkt, sock, from, pkt, "ACKNOWLEDGE MESSAGE"); - parse_message(pkt, pkt); + parse_message(pkt, conn, pkt); } else { printf("==============================================\n" diff --git a/siahsd.conf b/siahsd.conf new file mode 100644 index 0000000..57b9c7f --- /dev/null +++ b/siahsd.conf @@ -0,0 +1,11 @@ +# Place this file in /etc/siahsd.conf +[database] +driver = mysql +host = localhost +name = siahsd +username = root +password = + + +[siahs] +port = 4000 diff --git a/siahsd.h b/siahsd.h new file mode 100644 index 0000000..1919903 --- /dev/null +++ b/siahsd.h @@ -0,0 +1,9 @@ + +typedef struct { + char *database_host; + char *database_username; + char *database_password; + char *database_name; + char *database_driver; + gint siahs_port; +} configuration;