From 1ed17293bedaf5bf5182d863cd406d7eb66b4501 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 24 Mar 2019 19:22:30 +0100 Subject: Factor out socket operations (Fixes #13) --- backends/libmmbackend.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 backends/libmmbackend.c (limited to 'backends/libmmbackend.c') diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c new file mode 100644 index 0000000..6320611 --- /dev/null +++ b/backends/libmmbackend.c @@ -0,0 +1,117 @@ +#include "libmmbackend.h" + +void mmbackend_parse_hostspec(char* spec, char** host, char** port){ + size_t u = 0; + + if(!spec || !host || !port){ + return; + } + + *port = NULL; + + //skip leading spaces + for(; spec[u] && isspace(spec[u]); u++){ + } + + if(!spec[u]){ + *host = NULL; + return; + } + + *host = spec + u; + + //scan until string end or space + for(; spec[u] && !isspace(spec[u]); u++){ + } + + //if space, the rest should be the port + if(spec[u]){ + spec[u] = 0; + *port = spec + u + 1; + } +} + +int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){ + struct addrinfo* head; + struct addrinfo hints = { + .ai_family = AF_UNSPEC + }; + + int error = getaddrinfo(host, port, &hints, &head); + if(error || !head){ + fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(error)); + return 1; + } + + memcpy(addr, head->ai_addr, head->ai_addrlen); + if(len){ + *len = head->ai_addrlen; + } + + freeaddrinfo(head); + return 0; +} + +int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ + int fd = -1, status, yes = 1, flags; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = socktype, + .ai_flags = (listener ? AI_PASSIVE : 0) + }; + struct addrinfo *info, *addr_it; + + status = getaddrinfo(host, port, &hints, &info); + if(status){ + fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(status)); + return -1; + } + + //traverse the result list + for(addr_it = info; addr_it; addr_it = addr_it->ai_next){ + fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); + if(fd < 0){ + continue; + } + + //set required socket options + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to enable SO_REUSEADDR on socket\n"); + } + + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to enable SO_BROADCAST on socket\n"); + } + + yes = 0; + if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to disable IP_MULTICAST_LOOP on socket: %s\n", strerror(errno)); + } + + status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } + + break; + } + freeaddrinfo(info); + + if(!addr_it){ + fprintf(stderr, "Failed to create socket for %s port %s\n", host, port); + return -1; + } + + //set nonblocking + flags = fcntl(fd, F_GETFL, 0); + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ + fprintf(stderr, "Failed to set socket nonblocking\n"); + close(fd); + return -1; + } + + return fd; +} -- cgit v1.2.3 From 20a6882a063404858588596bd3f12bdd9e53460a Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 3 Aug 2019 18:42:39 +0200 Subject: Windows build compatiblity --- backends/libmmbackend.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'backends/libmmbackend.c') diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index 6320611..b27ebc5 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -53,7 +53,7 @@ int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* ad } int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ - int fd = -1, status, yes = 1, flags; + int fd = -1, status, yes = 1; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = socktype, @@ -106,12 +106,20 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ } //set nonblocking - flags = fcntl(fd, F_GETFL, 0); + #ifdef _WIN32 + u_long mode = 1; + if(ioctlsocket(fd, FIONBIO, &mode) != NO_ERROR){ + closesocket(fd); + return 1; + } + #else + int flags = fcntl(fd, F_GETFL, 0); if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ fprintf(stderr, "Failed to set socket nonblocking\n"); close(fd); return -1; } + #endif return fd; } -- cgit v1.2.3 From cf93d280af47aea1bf8bdafa30eabb2c2de005b8 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 10 Aug 2019 15:30:48 +0200 Subject: Implement stream client connections in libmmbackend (Fixes #19) --- backends/libmmbackend.c | 62 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 17 deletions(-) (limited to 'backends/libmmbackend.c') diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index b27ebc5..2fd3b8b 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -52,7 +52,7 @@ int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* ad return 0; } -int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ +int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uint8_t mcast){ int fd = -1, status, yes = 1; struct addrinfo hints = { .ai_family = AF_UNSPEC, @@ -80,20 +80,31 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ fprintf(stderr, "Failed to enable SO_REUSEADDR on socket\n"); } - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to enable SO_BROADCAST on socket\n"); - } + if(mcast){ + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to enable SO_BROADCAST on socket\n"); + } - yes = 0; - if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to disable IP_MULTICAST_LOOP on socket: %s\n", strerror(errno)); + yes = 0; + if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to disable IP_MULTICAST_LOOP on socket: %s\n", strerror(errno)); + } } - status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); - if(status < 0){ - close(fd); - continue; + if(listener){ + status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } + } + else{ + status = connect(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } } break; @@ -107,11 +118,11 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ //set nonblocking #ifdef _WIN32 - u_long mode = 1; - if(ioctlsocket(fd, FIONBIO, &mode) != NO_ERROR){ - closesocket(fd); - return 1; - } + u_long mode = 1; + if(ioctlsocket(fd, FIONBIO, &mode) != NO_ERROR){ + closesocket(fd); + return 1; + } #else int flags = fcntl(fd, F_GETFL, 0); if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ @@ -123,3 +134,20 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ return fd; } + +int mmbackend_send(int fd, uint8_t* data, size_t length){ + ssize_t total = 0, sent; + while(total < length){ + sent = send(fd, data + total, length - total, 0); + if(sent < 0){ + fprintf(stderr, "Failed to send: %s\n", strerror(errno)); + return 1; + } + total += sent; + } + return 0; +} + +int mmbackend_send_str(int fd, char* data){ + return mmbackend_send(fd, (uint8_t*) data, strlen(data)); +} -- cgit v1.2.3 From bb6111986bf7a997055287b916d0822957c5d13c Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 11 Aug 2019 20:29:17 +0200 Subject: Initial maweb backend --- backends/libmmbackend.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) (limited to 'backends/libmmbackend.c') diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index 2fd3b8b..c98cfe3 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -151,3 +151,220 @@ int mmbackend_send(int fd, uint8_t* data, size_t length){ int mmbackend_send_str(int fd, char* data){ return mmbackend_send(fd, (uint8_t*) data, strlen(data)); } + +json_type json_identify(char* json, size_t length){ + size_t n; + + //skip leading blanks + for(n = 0; json[n] && n < length && isspace(json[n]); n++){ + } + + if(n == length){ + return JSON_INVALID; + } + + switch(json[n]){ + case '{': + return JSON_OBJECT; + case '[': + return JSON_ARRAY; + case '"': + return JSON_STRING; + case '-': + case '+': + return JSON_NUMBER; + default: + //true false null number + if(!strncmp(json + n, "true", 4) + || !strncmp(json + n, "false", 5)){ + return JSON_BOOL; + } + else if(!strncmp(json + n, "null", 4)){ + return JSON_NULL; + } + //a bit simplistic but it should do + if(isdigit(json[n])){ + return JSON_NUMBER; + } + } + return JSON_INVALID; +} + +size_t json_validate(char* json, size_t length){ + switch(json_identify(json, length)){ + case JSON_STRING: + return json_validate_string(json, length); + case JSON_ARRAY: + return json_validate_array(json, length); + case JSON_OBJECT: + return json_validate_object(json, length); + case JSON_INVALID: + return 0; + default: + return json_validate_value(json, length); + } +} + +size_t json_validate_string(char* json, size_t length){ + size_t string_length = 0, offset; + + for(offset = 0; json[offset] && offset < length && json[offset] != '"'; offset++){ + } + + if(offset == length){ + return 0; + } + + //find terminating quotation mark not preceded by escape + for(string_length = 1; offset + string_length < length + && isprint(json[offset + string_length]) + && (json[offset + string_length] != '"' || json[offset + string_length - 1] == '\\'); string_length++){ + } + + //complete string found + if(json[offset + string_length] == '"' && json[offset + string_length - 1] != '\\'){ + return offset + string_length + 1; + } + + return 0; +} + +size_t json_validate_array(char* json, size_t length){ + //TODO + return 0; +} + +size_t json_validate_object(char* json, size_t length){ + //TODO + return 0; +} + +size_t json_validate_value(char* json, size_t length){ + //TODO + return 0; +} + +size_t json_obj_offset(char* json, char* key){ + size_t offset = 0; + uint8_t match = 0; + + //skip whitespace + for(offset = 0; json[offset] && isspace(json[offset]); offset++){ + } + + if(json[offset] != '{'){ + return 0; + } + offset++; + + while(json_identify(json + offset, strlen(json + offset)) == JSON_STRING){ + //skip to key begin + for(; json[offset] && json[offset] != '"'; offset++){ + } + + if(!strncmp(json + offset + 1, key, strlen(key)) && json[offset + 1 + strlen(key)] == '"'){ + //key found + match = 1; + } + + offset += json_validate_string(json + offset, strlen(json + offset)); + + //skip to value separator + for(; json[offset] && json[offset] != ':'; offset++){ + } + + //skip whitespace + for(offset++; json[offset] && isspace(json[offset]); offset++){ + } + + if(match){ + return offset; + } + + //add length of value + offset += json_validate(json + offset, strlen(json + offset)); + + //find comma or closing brace + for(; json[offset] && json[offset] != ',' && json[offset] != '}'; offset++){ + } + + if(json[offset] == ','){ + offset++; + } + } + + return 0; +} + +json_type json_obj(char* json, char* key){ + size_t offset = json_obj_offset(json, key); + if(offset){ + return json_identify(json + offset, strlen(json + offset)); + } + return JSON_INVALID; +} + +uint8_t json_obj_bool(char* json, char* key, uint8_t fallback){ + size_t offset = json_obj_offset(json, key); + if(offset){ + if(!strncmp(json + offset, "true", 4)){ + return 1; + } + if(!strncmp(json + offset, "false", 5)){ + return 0; + } + } + return fallback; +} + +int64_t json_obj_int(char* json, char* key, int64_t fallback){ + char* next_token = NULL; + int64_t result; + size_t offset = json_obj_offset(json, key); + if(offset){ + result = strtol(json + offset, &next_token, 10); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + +double json_obj_double(char* json, char* key, double fallback){ + char* next_token = NULL; + int64_t result; + size_t offset = json_obj_offset(json, key); + if(offset){ + result = strtod(json + offset, &next_token); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + +char* json_obj_str(char* json, char* key, size_t* length){ + size_t offset = json_obj_offset(json, key), raw_length; + if(offset){ + raw_length = json_validate_string(json + offset, strlen(json + offset)); + if(length){ + *length = raw_length - 2; + } + return json + offset + 1; + } + return NULL; +} + +char* json_obj_strdup(char* json, char* key){ + size_t offset = json_obj_offset(json, key), raw_length; + char* rv = NULL; + if(offset){ + raw_length = json_validate_string(json + offset, strlen(json + offset)); + rv = calloc(raw_length - 1, sizeof(char)); + if(rv){ + memcpy(rv, json + offset + 1, raw_length - 2); + } + return rv; + } + return NULL; +} -- cgit v1.2.3 From 8b016f61a4b3d3be0c7b1e311209ab991276af0c Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 22 Aug 2019 21:13:48 +0200 Subject: Implement input for the maweb backend (with a few limitations) --- backends/libmmbackend.c | 241 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 227 insertions(+), 14 deletions(-) (limited to 'backends/libmmbackend.c') diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index c98cfe3..ccbeb52 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -208,10 +208,11 @@ size_t json_validate(char* json, size_t length){ size_t json_validate_string(char* json, size_t length){ size_t string_length = 0, offset; - for(offset = 0; json[offset] && offset < length && json[offset] != '"'; offset++){ + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ } - if(offset == length){ + if(offset == length || json[offset] != '"'){ return 0; } @@ -230,17 +231,122 @@ size_t json_validate_string(char* json, size_t length){ } size_t json_validate_array(char* json, size_t length){ - //TODO + size_t offset = 0; + + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ + } + + if(offset == length || json[offset] != '['){ + return 0; + } + + for(offset++; offset < length; offset++){ + offset += json_validate(json + offset, length - offset); + + //skip trailing whitespace, find terminator + for(; offset < length && isspace(json[offset]); offset++){ + } + + if(json[offset] == ','){ + continue; + } + + if(json[offset] == ']'){ + return offset + 1; + } + + break; + } + return 0; } size_t json_validate_object(char* json, size_t length){ - //TODO + size_t offset = 0; + + //skip whitespace + for(offset = 0; json[offset] && isspace(json[offset]); offset++){ + } + + if(offset == length || json[offset] != '{'){ + return 0; + } + + for(offset++; offset < length; offset++){ + if(json_identify(json + offset, length - offset) != JSON_STRING){ + //still could be an empty object... + for(; offset < length && isspace(json[offset]); offset++){ + } + if(json[offset] == '}'){ + return offset + 1; + } + return 0; + } + offset += json_validate(json + offset, length - offset); + + //find value separator + for(; offset < length && isspace(json[offset]); offset++){ + } + + if(json[offset] != ':'){ + return 0; + } + + offset++; + offset += json_validate(json + offset, length - offset); + + //skip trailing whitespace + for(; json[offset] && isspace(json[offset]); offset++){ + } + + if(json[offset] == '}'){ + return offset + 1; + } + else if(json[offset] != ','){ + return 0; + } + } return 0; } size_t json_validate_value(char* json, size_t length){ - //TODO + size_t offset = 0, value_length; + + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ + } + + if(offset == length){ + return 0; + } + + //match complete values + if(length - offset >= 4 && !strncmp(json + offset, "null", 4)){ + return offset + 4; + } + else if(length - offset >= 4 && !strncmp(json + offset, "true", 4)){ + return offset + 4; + } + else if(length - offset >= 5 && !strncmp(json + offset, "false", 5)){ + return offset + 5; + } + + if(json[offset] == '-' || isdigit(json[offset])){ + //json number parsing is dumb. + for(value_length = 1; offset + value_length < length && + (isdigit(json[offset + value_length]) + || json[offset + value_length] == '+' + || json[offset + value_length] == '-' + || json[offset + value_length] == '.' + || tolower(json[offset + value_length]) == 'e'); value_length++){ + } + + if(value_length > 0){ + return offset + value_length; + } + } + return 0; } @@ -284,13 +390,51 @@ size_t json_obj_offset(char* json, char* key){ //add length of value offset += json_validate(json + offset, strlen(json + offset)); - //find comma or closing brace - for(; json[offset] && json[offset] != ',' && json[offset] != '}'; offset++){ + //skip trailing whitespace + for(; json[offset] && isspace(json[offset]); offset++){ } if(json[offset] == ','){ offset++; + continue; + } + + break; + } + + return 0; +} + +size_t json_array_offset(char* json, uint64_t key){ + size_t offset = 0, index = 0; + + //skip leading whitespace + for(offset = 0; json[offset] && isspace(json[offset]); offset++){ + } + + if(json[offset] != '['){ + return 0; + } + + for(offset++; index <= key; offset++){ + //skip whitespace + for(; json[offset] && isspace(json[offset]); offset++){ + } + + if(index == key){ + return offset; + } + + offset += json_validate(json + offset, strlen(json + offset)); + + //skip trailing whitespace, find terminator + for(; json[offset] && isspace(json[offset]); offset++){ } + + if(json[offset] != ','){ + break; + } + index++; } return 0; @@ -304,6 +448,14 @@ json_type json_obj(char* json, char* key){ return JSON_INVALID; } +json_type json_array(char* json, uint64_t key){ + size_t offset = json_array_offset(json, key); + if(offset){ + return json_identify(json + offset, strlen(json + offset)); + } + return JSON_INVALID; +} + uint8_t json_obj_bool(char* json, char* key, uint8_t fallback){ size_t offset = json_obj_offset(json, key); if(offset){ @@ -317,6 +469,19 @@ uint8_t json_obj_bool(char* json, char* key, uint8_t fallback){ return fallback; } +uint8_t json_array_bool(char* json, uint64_t key, uint8_t fallback){ + size_t offset = json_array_offset(json, key); + if(offset){ + if(!strncmp(json + offset, "true", 4)){ + return 1; + } + if(!strncmp(json + offset, "false", 5)){ + return 0; + } + } + return fallback; +} + int64_t json_obj_int(char* json, char* key, int64_t fallback){ char* next_token = NULL; int64_t result; @@ -332,7 +497,7 @@ int64_t json_obj_int(char* json, char* key, int64_t fallback){ double json_obj_double(char* json, char* key, double fallback){ char* next_token = NULL; - int64_t result; + double result; size_t offset = json_obj_offset(json, key); if(offset){ result = strtod(json + offset, &next_token); @@ -343,6 +508,32 @@ double json_obj_double(char* json, char* key, double fallback){ return fallback; } +int64_t json_array_int(char* json, uint64_t key, int64_t fallback){ + char* next_token = NULL; + int64_t result; + size_t offset = json_array_offset(json, key); + if(offset){ + result = strtol(json + offset, &next_token, 10); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + +double json_array_double(char* json, uint64_t key, double fallback){ + char* next_token = NULL; + double result; + size_t offset = json_array_offset(json, key); + if(offset){ + result = strtod(json + offset, &next_token); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + char* json_obj_str(char* json, char* key, size_t* length){ size_t offset = json_obj_offset(json, key), raw_length; if(offset){ @@ -356,15 +547,37 @@ char* json_obj_str(char* json, char* key, size_t* length){ } char* json_obj_strdup(char* json, char* key){ - size_t offset = json_obj_offset(json, key), raw_length; - char* rv = NULL; + size_t len = 0; + char* value = json_obj_str(json, key, &len), *rv = NULL; + if(len){ + rv = calloc(len + 1, sizeof(char)); + if(rv){ + memcpy(rv, value, len); + } + } + return rv; +} + +char* json_array_str(char* json, uint64_t key, size_t* length){ + size_t offset = json_array_offset(json, key), raw_length; if(offset){ raw_length = json_validate_string(json + offset, strlen(json + offset)); - rv = calloc(raw_length - 1, sizeof(char)); - if(rv){ - memcpy(rv, json + offset + 1, raw_length - 2); + if(length){ + *length = raw_length - 2; } - return rv; + return json + offset + 1; } return NULL; } + +char* json_array_strdup(char* json, uint64_t key){ + size_t len = 0; + char* value = json_array_str(json, key, &len), *rv = NULL; + if(len){ + rv = calloc(len + 1, sizeof(char)); + if(rv){ + memcpy(rv, value, len); + } + } + return rv; +} -- cgit v1.2.3