aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends/libmmbackend.c
diff options
context:
space:
mode:
Diffstat (limited to 'backends/libmmbackend.c')
-rw-r--r--backends/libmmbackend.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c
new file mode 100644
index 0000000..ccbeb52
--- /dev/null
+++ b/backends/libmmbackend.c
@@ -0,0 +1,583 @@
+#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, uint8_t mcast){
+ int fd = -1, status, yes = 1;
+ 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");
+ }
+
+ 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));
+ }
+ }
+
+ 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;
+ }
+ freeaddrinfo(info);
+
+ if(!addr_it){
+ fprintf(stderr, "Failed to create socket for %s port %s\n", host, port);
+ return -1;
+ }
+
+ //set nonblocking
+ #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;
+}
+
+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));
+}
+
+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;
+
+ //skip leading whitespace
+ for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){
+ }
+
+ if(offset == length || json[offset] != '"'){
+ 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){
+ 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){
+ 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){
+ 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;
+}
+
+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));
+
+ //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;
+}
+
+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;
+}
+
+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){
+ if(!strncmp(json + offset, "true", 4)){
+ return 1;
+ }
+ if(!strncmp(json + offset, "false", 5)){
+ return 0;
+ }
+ }
+ 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;
+ 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;
+ double 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;
+}
+
+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){
+ 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 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));
+ if(length){
+ *length = raw_length - 2;
+ }
+ 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;
+}