From: Mike Brady Date: Mon, 18 Dec 2017 18:50:01 +0000 (+0000) Subject: Add tinyhttp code for sending and reading responses. Clean up some dacp routines... X-Git-Tag: 3.2d22~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1637a79dd6d2fab8423dcee70d587de53c2a2e06;p=thirdparty%2Fshairport-sync.git Add tinyhttp code for sending and reading responses. Clean up some dacp routines. Most new and modified rooutines untested. --- diff --git a/LICENSES b/LICENSES index 583fdf4f..f5fed951 100644 --- a/LICENSES +++ b/LICENSES @@ -8,6 +8,11 @@ tinysvcmdns.c, tinysvcmdns.h: All rights reserved. see tinysvcmdns.[ch] for full license text +tinyhttp + Copyright 2012 Matthew Endsley + All rights reserved + see the tinyhttp/LICENSE for full license text + Shairport: Copyright (c) 2011-2013 James Laird diff --git a/Makefile.am b/Makefile.am index 48ef77aa..9762325d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -88,7 +88,7 @@ dbus-interface.c: org.gnome.ShairportSync.xml endif if USE_DBUS_CORE_AND_DACP -shairport_sync_SOURCES += dacp.c +shairport_sync_SOURCES += dacp.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c endif if USE_MPRIS diff --git a/audio_pa.c b/audio_pa.c index 9bc26952..aef433ed 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -97,14 +97,14 @@ static int init(int argc, char **argv) { // Get a mainloop and its context mainloop = pa_threaded_mainloop_new(); - if (mainloop==NULL) + if (mainloop == NULL) die("could not create a pa_threaded_mainloop."); mainloop_api = pa_threaded_mainloop_get_api(mainloop); if (config.pa_application_name) context = pa_context_new(mainloop_api, config.pa_application_name); else context = pa_context_new(mainloop_api, "Shairport Sync"); - if (context==NULL) + if (context == NULL) die("could not create a new context for pulseaudio."); // Set a callback so we can wait for the context to be ready pa_context_set_state_callback(context, &context_state_cb, mainloop); @@ -116,14 +116,15 @@ static int init(int argc, char **argv) { if (pa_threaded_mainloop_start(mainloop) != 0) die("could not start the pulseaudio threaded mainloop"); if (pa_context_connect(context, NULL, 0, NULL) != 0) - die("failed to connect to the pulseaudio context -- the error message is \"%s\".",pa_strerror(pa_context_errno(context))); - + die("failed to connect to the pulseaudio context -- the error message is \"%s\".", + pa_strerror(pa_context_errno(context))); // Wait for the context to be ready for (;;) { pa_context_state_t context_state = pa_context_get_state(context); if (!PA_CONTEXT_IS_GOOD(context_state)) - die("pa context is not good -- the error message \"%s\".",pa_strerror(pa_context_errno(context))); + die("pa context is not good -- the error message \"%s\".", + pa_strerror(pa_context_errno(context))); if (context_state == PA_CONTEXT_READY) break; pa_threaded_mainloop_wait(mainloop); @@ -176,13 +177,16 @@ static void start(int sample_rate, int sample_format) { // Connect stream to the default audio output sink if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0) - die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",pa_strerror(pa_context_errno(context))); + die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".", + pa_strerror(pa_context_errno(context))); // Wait for the stream to be ready for (;;) { pa_stream_state_t stream_state = pa_stream_get_state(stream); - if(!PA_STREAM_IS_GOOD(stream_state)) - die("stream state is no longer good while waiting for stream to become ready -- the error message is \"%s\".",pa_strerror(pa_context_errno(context))); + if (!PA_STREAM_IS_GOOD(stream_state)) + die("stream state is no longer good while waiting for stream to become ready -- the error " + "message is \"%s\".", + pa_strerror(pa_context_errno(context))); if (stream_state == PA_STREAM_READY) break; pa_threaded_mainloop_wait(mainloop); diff --git a/common.h b/common.h index 24f37676..2d1547c6 100644 --- a/common.h +++ b/common.h @@ -82,7 +82,7 @@ enum sps_format_t { typedef struct { config_t *cfg; double airplay_volume; // stored here for reloading when necessary - char *appName; // normally the app is called shairport-syn, but it may be symlinked + char *appName; // normally the app is called shairport-syn, but it may be symlinked char *password; char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running // on host %h" diff --git a/dacp.c b/dacp.c index 72761146..4407a77f 100644 --- a/dacp.c +++ b/dacp.c @@ -39,6 +39,8 @@ #include #include +#include "tinyhttp/http.h" + typedef struct { uint16_t port; short connection_family; // AF_INET6 or AF_INET @@ -50,12 +52,58 @@ typedef struct { pthread_t dacp_monitor_thread; dacp_server_record dacp_server; +// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.) +struct HttpResponse { + void *body; // this will be a malloc'ed pointer + ssize_t malloced_size; // this will be its allocated size + ssize_t size; // the current size of the content + int code; +}; + +static void *response_realloc(void *opaque, void *ptr, int size) { return realloc(ptr, size); } + +static void response_body(void *opaque, const char *data, int size) { + struct HttpResponse *response = (struct HttpResponse *)opaque; + + ssize_t space_available = response->malloced_size - response->size; + if (space_available < size) { + printf("Getting more space for the response -- need %d bytes but only %d bytes left.\n", size, + size - space_available); + ssize_t size_requested = size - space_available + response->malloced_size + 16384; + void *t = realloc(response->body, size_requested); + response->malloced_size = size_requested; + if (t) + response->body = t; + else { + printf("Can't allocate any more space for parser.\n"); + exit(-1); + } + } + memcpy(response->body + response->size, data, size); + response->size += size; +} + +static void response_header(void *opaque, const char *ckey, int nkey, const char *cvalue, + int nvalue) { /* example doesn't care about headers */ +} + +static void response_code(void *opaque, int code) { + struct HttpResponse *response = (struct HttpResponse *)opaque; + response->code = code; +} + +static const struct http_funcs responseFuncs = { + response_realloc, response_body, response_header, response_code, +}; + static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER; -ssize_t dacp_send_command(const char *command, char *response, size_t max_response_length) { - ssize_t reply_size = -1; +int dacp_send_command(const char *command, char **body, size_t *bodysize) { + + // will malloc space for the body or set it to NULL -- the caller should free it. + // try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed // to do it. struct timespec mutex_wait_time; @@ -65,20 +113,19 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon struct addrinfo hints, *res; int sockfd; - char message[20000], server_reply[2000], portstring[10], server[256]; - memset(&message, 0, sizeof(message)); - if ((response) && (max_response_length)) - memset(response, 0, max_response_length); - else - memset(&server_reply, 0, sizeof(server_reply)); - memset(&portstring, 0, sizeof(portstring)); + struct HttpResponse response; + response.body = NULL; + response.malloced_size = 0; + response.size = 0; + response.code = 400; // client error + char portstring[10], server[256], message[1024]; + memset(&portstring, 0, sizeof(portstring)); if (dacp_server.connection_family == AF_INET6) { sprintf(server, "%s%%%u", dacp_server.ip_string, dacp_server.scope_id); } else { strcpy(server, dacp_server.ip_string); } - sprintf(portstring, "%u", dacp_server.port); // first, load up address structs with getaddrinfo(): @@ -115,28 +162,74 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon // Send command - if (send(sockfd, message, strlen(message), 0) < 0) { + if (send(sockfd, message, strlen(message), 0) != strlen(message)) { debug(1, "Send failed"); - } + } else { - // Receive a reply from the server - if ((response) && (max_response_length)) - reply_size = recv(sockfd, response, max_response_length, 0); - else - reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0); - if (reply_size < 0) { - debug(1, "recv failed"); + response.body = malloc(2048); // it can resize this if necessary + response.malloced_size = 2048; + + struct http_roundtripper rt; + http_init(&rt, responseFuncs, &response); + + int needmore = 1; + int looperror = 0; + char buffer[1024]; + while (needmore && !looperror) { + const char *data = buffer; + int ndata = recv(sockfd, buffer, sizeof(buffer), 0); + if (ndata <= 0) { + debug(1, "Error receiving data."); + free(response.body); + response.body = NULL; + response.malloced_size = 0; + response.size = 0; + looperror = 1; + } + + while (needmore && ndata && !looperror) { + int read; + needmore = http_data(&rt, data, ndata, &read); + ndata -= read; + data += read; + } + } + + if (http_iserror(&rt)) { + debug(1, "Error parsing data."); + free(response.body); + response.body = NULL; + response.malloced_size = 0; + response.size = 0; + } + + http_free(&rt); + close(sockfd); } - close(sockfd); } } pthread_mutex_unlock(&dacp_conversation_lock); } else { - debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?"); + // debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?"); + response.code = 408; // not strictly correct } - return reply_size; + *body = response.body; + *bodysize = response.size; + return response.code; } +int send_simple_dacp_command(const char *command) { + int reply = 0; + char *server_reply = NULL; + debug(1, "Sending command \"%s\".", command); + ssize_t reply_size = 0; + reply = dacp_send_command(command, &server_reply, &reply_size); + if (server_reply) { + free(server_reply); + server_reply = NULL; + } + return reply; +} // this will be running on the thread of its caller, not of the conversation thread... void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conversation thread that @@ -155,9 +248,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conver void *dacp_monitor_thread_code(void *na) { int scan_index = 0; - char server_reply[2000]; + char server_reply[10000]; debug(1, "DACP monitor thread started."); // wait until we get a valid port number to begin monitoring it + int32_t revision_number = 1; while (1) { pthread_mutex_lock(&dacp_server_information_lock); while (dacp_server.port == 0) { @@ -165,37 +259,114 @@ void *dacp_monitor_thread_code(void *na) { pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock); } pthread_mutex_unlock(&dacp_server_information_lock); - //debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id, + // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id, // dacp_server.ip_string, dacp_server.port, scan_index++); + ssize_t le; + char *response = NULL; + int32_t item_size; + char command[1024] = ""; + snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d", revision_number); + // debug(1,"Command: \"%s\"",command); + int result = dacp_send_command(command, &response, &le); + if (result == 200) { + char *sp = response; + if (le >= 0) { + // here start looking for the contents of the status update + if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status + sp -= item_size; // drop down into the array -- don't skip over it + le -= 8; + char typestring[5]; + while (le >= 8) { + uint32_t type = dacp_tlv_crawl(&sp, &item_size); + + *(uint32_t *)typestring = htonl(type); + typestring[4] = 0; + printf("\"%s\" %4d", typestring, item_size); + + le -= item_size + 8; + char *t; + char u; + char *st; + int32_t r; + uint64_t s, v; + int i; + switch (type) { + + case 'mstt': + case 'cant': + case 'cast': + case 'cmmk': + case 'caas': + case 'caar': + case 'astm': + t = sp - item_size; + r = ntohl(*(int32_t *)(t)); + printf(" %d", r); + printf(" (0x"); + t = sp - item_size; + for (i = 0; i < item_size; i++) { + printf("%02x", *t); + t++; + } + printf(")"); + break; + case 'cmsr': + t = sp - item_size; + revision_number = ntohl(*(int32_t *)(t)); + printf(" Serial Number: %d", revision_number); + break; + case 'cann': + case 'cana': + case 'canl': + case 'cang': + t = sp - item_size; + st = strndup(t, item_size); + printf(" \"%s\"", st); + free(st); + break; + case 'asai': + t = sp - item_size; + s = ntohl(*(uint32_t *)(t)); + s = s << 32; + t += 4; + v = (ntohl(*(uint32_t *)(t))) & 0xffffffff; + s += v; + printf(" %llu", s); + printf(" (0x"); + t = sp - item_size; + for (i = 0; i < item_size; i++) { + printf("%02x", *t); + t++; + } + printf(")"); + break; + default: + printf(" 0x"); + t = sp - item_size; + for (i = 0; i < item_size; i++) { + printf("%02x", *t); + t++; + } + break; + } + printf("\n"); + } + } else { + printf("Status Update not found.\n"); + } - ssize_t reply_size = - dacp_send_command("playstatusupdate", server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - // not interested in the response. - if (strstr(server_reply, "HTTP/1.1 204") == server_reply) { - debug(1,"Client response is No Content"); - } else if (strstr(server_reply, "HTTP/1.1 501") == server_reply) { - debug(1,"Client response is \"Not Implemented\". DACP Server has disconnnected."); - dacp_server.port = 0; // stop scanning until the next time it's used -/* } else { - debug(1, - "Client request to server responded with %d characters starting with this response:", - strlen(server_reply)); - int i; - for (i = 0; i < reply_size; i++) - if (server_reply[i] < ' ') - debug(1, "%d %02x", i, server_reply[i]); - else - debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]); - // sprintf((char *)message + 2 * i, "%02x", server_reply[i]); - // debug(1,"Content is \"%s\".",message); -*/ + debug(1, "Can't find any content in playerstatusupdate request"); } } else { - debug(1, "Error at rtp_send_client_command"); + if (result != 403) + debug(1, "Unexpected response %d to playerstatusupdate request", result); } - sleep(3); + if (response) { + free(response); + response = NULL; + }; + sleep(1); } debug(1, "DACP monitor thread exiting."); pthread_exit(NULL); @@ -218,278 +389,145 @@ uint32_t dacp_tlv_crawl(char **p, int32_t *length) { return type; } -ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response, - size_t max_response_length) { - ssize_t reply_size = -1; - // try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed - // to do it. - struct timespec mutex_wait_time; - mutex_wait_time.tv_sec = 0; - mutex_wait_time.tv_nsec = 20000000; // 20 ms - - if (conn->rtp_running) { - if (conn->dacp_port == 0) { - debug(1, "Can't send a remote request: no valid active remote."); - } else { - - struct addrinfo hints, *res; - int sockfd; - - char message[20000], server_reply[2000], portstring[10], server[256]; - memset(&message, 0, sizeof(message)); - if ((response) && (max_response_length)) - memset(response, 0, max_response_length); - else - memset(&server_reply, 0, sizeof(server_reply)); - memset(&portstring, 0, sizeof(portstring)); - - if (conn->connection_ip_family == AF_INET6) { - sprintf(server, "%s%%%u", conn->client_ip_string, conn->self_scope_id); - } else { - strcpy(server, conn->client_ip_string); - } - - sprintf(portstring, "%u", conn->dacp_port); - - // first, load up address structs with getaddrinfo(): - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - getaddrinfo(server, portstring, &hints, &res); - - // only do this one at a time -- not sure it is necessary, but better safe than sorry - - int mutex_reply = pthread_mutex_timedlock(&dacp_conversation_lock, &mutex_wait_time); - if (mutex_reply == 0) { - - // make a socket: - - sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - - if (sockfd == -1) { - debug(1, "Could not create socket"); - } else { - - // connect! - - if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - debug(1, "connect failed. Error"); - } else { - - sprintf(message, - "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n", - command, conn->client_ip_string, conn->dacp_port, conn->dacp_active_remote); - - // Send command - - if (send(sockfd, message, strlen(message), 0) < 0) { - debug(1, "Send failed"); - } - - // Receive a reply from the server - if ((response) && (max_response_length)) - reply_size = recv(sockfd, response, max_response_length, 0); - else - reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0); - if (reply_size < 0) { - debug(1, "recv failed"); - } - close(sockfd); - } - } - pthread_mutex_unlock(&dacp_conversation_lock); - } else { - debug(1, - "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?"); - } - } - } else { - debug(1, "Request to pause non-existent play stream -- ignored."); - } - return reply_size; -} - int32_t dacp_get_client_volume(rtsp_conn_info *conn) { - char server_reply[2000]; + char *server_reply = NULL; int32_t overall_volume = -1; - - ssize_t reply_size = dacp_send_client_command(conn, "getproperty?properties=dmcp.volume", - server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay - char *sp = strstr(server_reply, "Content-Length: "); - if (sp) { // there is something there - sp += strlen("Content-Length: "); - int le = atoi(sp); - if (le == 32) { - sp = strstr(sp, "\r") + 4 + 28; - uint32_t *np = (uint32_t *)sp; - overall_volume = ntohl(*np); - // debug(1,"Overall Volume is %d.",overall_volume); - } else { - debug(1, "Can't find the volume tag"); - } - } else { - debug(1, "Can't find any content in volume control request"); - } - } else { - debug(1, "Unexpected response to dacp volume control request"); - } + ssize_t reply_size; + int response = + dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size); + if (response == 200) { // if we get an okay + uint32_t *np = (uint32_t *)server_reply; + overall_volume = ntohl(*np); + // debug(1,"Overall Volume is %d.",overall_volume); + free(server_reply); } else { - debug(1, "Error asking for dacp volume."); + debug(1, "Unexpected response %d to dacp volume control request", response); } return overall_volume; } int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) { - char server_reply[2000]; - int reply = -1; // will bve fixed if there is no problem char message[1000]; memset(message, 0, sizeof(message)); sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo); // debug(1,"sending \"%s\"",message); - ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - if (strstr(server_reply, "HTTP/1.1 204") == server_reply) { - // debug(1,"dacp_set_include_speaker_volume successful."); - reply = 0; - } - } else { - debug(1, "dacp_set_include_speaker_volume unsuccessful."); - } - return reply; + return send_simple_dacp_command(message); + // should return 204 } int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) { - char server_reply[2000]; - int reply = -1; // will bve fixed if there is no problem char message[1000]; memset(message, 0, sizeof(message)); sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo); // debug(1,"sending \"%s\"",message); - ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - if (strstr(server_reply, "HTTP/1.1 204") == server_reply) { - // debug(1,"dacp_set_speaker_volume successful."); - reply = 0; - } - } else { - debug(1, "dacp_set_speaker_volume unsuccessful."); - } - return reply; + return send_simple_dacp_command(message); + // should return 204 } int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info, int max_size_of_array) { - char server_reply[2000]; + char *server_reply = NULL; int speaker_index = -1; // will be incremented before use int reply = -1; // will bve fixed if there is no problem - ssize_t reply_size = - dacp_send_client_command(conn, "getspeakers", server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay - char *sp = strstr(server_reply, "Content-Length: "); - if (sp) { // there is something there - sp += strlen("Content-Length: "); - int32_t le = atoi(sp); - int32_t item_size = 0; - sp = strstr(sp, "\r") + 4; - if (dacp_tlv_crawl(&sp, &item_size) == 'casp') { - // debug(1,"Speakers:",item_size); - sp -= item_size; // drop down into the array -- don't skip over it - le -= 8; - while (le >= 8) { - uint32_t type = dacp_tlv_crawl(&sp, &item_size); - if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it - // debug(1,">>>> Dictionary:"); - sp -= item_size; - le -= 8; - speaker_index++; - if (speaker_index == max_size_of_array) - return -1; // too many speakers - speaker_info[speaker_index].active = 0; - speaker_info[speaker_index].speaker_number = 0; - speaker_info[speaker_index].volume = 0; - speaker_info[speaker_index].name = NULL; - } else { - le -= item_size + 8; - char *t; - char u; - int32_t r; - int64_t s, v; - switch (type) { - case 'minm': - t = sp - item_size; - speaker_info[speaker_index].name = strndup(t, item_size); - // debug(1," \"%s\"",speaker_info[speaker_index].name); - break; - /* - case 'cads': - t = sp-item_size; - r = ntohl(*(int32_t*)(t)); - //debug(1,"CADS: \"%d\".",r); - break; - */ - case 'cmvo': - t = sp - item_size; - r = ntohl(*(int32_t *)(t)); - speaker_info[speaker_index].volume = r; - // debug(1,"Volume: \"%d\".",r); - break; - case 'msma': - t = sp - item_size; - s = ntohl(*(uint32_t *)(t)); - s = s << 32; - t += 4; - v = (ntohl(*(uint32_t *)(t))) & 0xffffffff; - s += v; - speaker_info[speaker_index].speaker_number = s; - // debug(1,"Speaker machine number: %ld",s); - break; - - case 'caia': - speaker_info[speaker_index].active = 1; - break; - /* - case 'caip': - case 'cavd': - case 'caiv': - t = sp-item_size; - u = *t; - //debug(1,"Value: \"%d\".",u); - break; - */ - default: - break; - } + ssize_t le; + + int response = dacp_send_command("getspeakers", &server_reply, &le); + if (response == 200) { + char *sp = server_reply; + int32_t item_size; + if (le >= 8) { + if (dacp_tlv_crawl(&sp, &item_size) == 'casp') { + // debug(1,"Speakers:",item_size); + sp -= item_size; // drop down into the array -- don't skip over it + le -= 8; + while (le >= 8) { + uint32_t type = dacp_tlv_crawl(&sp, &item_size); + if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it + // debug(1,">>>> Dictionary:"); + sp -= item_size; + le -= 8; + speaker_index++; + if (speaker_index == max_size_of_array) + return -1; // too many speakers + speaker_info[speaker_index].active = 0; + speaker_info[speaker_index].speaker_number = 0; + speaker_info[speaker_index].volume = 0; + speaker_info[speaker_index].name = NULL; + } else { + le -= item_size + 8; + char *t; + char u; + int32_t r; + int64_t s, v; + switch (type) { + case 'minm': + t = sp - item_size; + speaker_info[speaker_index].name = strndup(t, item_size); + // debug(1," \"%s\"",speaker_info[speaker_index].name); + break; + /* + case 'cads': + t = sp-item_size; + r = ntohl(*(int32_t*)(t)); + //debug(1,"CADS: \"%d\".",r); + break; + */ + case 'cmvo': + t = sp - item_size; + r = ntohl(*(int32_t *)(t)); + speaker_info[speaker_index].volume = r; + // debug(1,"Volume: \"%d\".",r); + break; + case 'msma': + t = sp - item_size; + s = ntohl(*(uint32_t *)(t)); + s = s << 32; + t += 4; + v = (ntohl(*(uint32_t *)(t))) & 0xffffffff; + s += v; + speaker_info[speaker_index].speaker_number = s; + // debug(1,"Speaker machine number: %ld",s); + break; + + case 'caia': + speaker_info[speaker_index].active = 1; + break; + /* + case 'caip': + case 'cavd': + case 'caiv': + t = sp-item_size; + u = *t; + //debug(1,"Value: \"%d\".",u); + break; + */ + default: + break; } } - // debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1); - reply = speaker_index + 1; // number of speaker entries in the array - } else { - debug(1, "Speaker array not found."); } - /* - int i; - for (i=0;i #include +#include #include "player.h" static pthread_mutex_t dacp_server_information_lock; static pthread_cond_t dacp_server_information_cv; - typedef struct dacp_speaker_stuff { int64_t speaker_number; int active; @@ -22,12 +21,13 @@ void dacp_monitor_start(); uint32_t dacp_tlv_crawl( char **p, int32_t *length); // return the code of the next TLV entity and advance the pointer beyond it. -ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response, - size_t max_response_length); + int32_t dacp_get_client_volume(rtsp_conn_info *conn); // return the overall volume from the client int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo); int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo); int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_array, int max_size_of_array); -void set_dacp_server_information(rtsp_conn_info* conn); // tell the DACP conversation thread that the dacp server information has been set or changed - +void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that + // the dacp server information has been set + // or changed +int send_simple_dacp_command(const char *command); diff --git a/dbus-service.c b/dbus-service.c index 6d1a72c8..0cc7d1ce 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -142,37 +142,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gpointer user_data) { static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gchar *command, gpointer user_data) { debug(1, "RemoteCommand with command \"%s\".", command); - if (playing_conn) { - char server_reply[2000]; - ssize_t reply_size = - dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - // not interested in the response. - // if (strstr(server_reply, "HTTP/1.1 204") == server_reply) { - // debug(1,"Client response is No Content"); - // } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) { - // debug("Client response is OK, with content"); - // } else { - - if (strstr(server_reply, "HTTP/1.1 204") != server_reply) { - debug(1, - "Client request to server responded with %d characters starting with this response:", - strlen(server_reply)); - int i; - for (i = 0; i < reply_size; i++) - if (server_reply[i] < ' ') - debug(1, "%d %02x", i, server_reply[i]); - else - debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]); - // sprintf((char *)message + 2 * i, "%02x", server_reply[i]); - // debug(1,"Content is \"%s\".",message); - } - } else { - debug(1, "Error at rtp_send_client_command"); - } - } else { - debug(1, "no thread playing -- RemoteCommand ignored."); - } + send_simple_dacp_command(const char *command); shairport_sync_complete_remote_command(skeleton, invocation); return TRUE; } diff --git a/definitions.h b/definitions.h index 5fe42b41..e705144c 100644 --- a/definitions.h +++ b/definitions.h @@ -33,5 +33,4 @@ #define SAFAMILY sa_family #endif - #endif // _DEFINITIONS_H diff --git a/mdns.h b/mdns.h index a55eea98..b720c6a9 100644 --- a/mdns.h +++ b/mdns.h @@ -2,8 +2,8 @@ #define _MDNS_H #include "config.h" -#include #include +#include extern int mdns_pid; diff --git a/mdns_avahi.c b/mdns_avahi.c index 5a957a57..d3927059 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -69,7 +69,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { assert(r); - + rtsp_conn_info *conn = (rtsp_conn_info *)userdata; dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer; @@ -141,8 +141,9 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah if (conn->dacp_id != 0) { debug(1, "Client's DACP status withdrawn."); conn->dacp_port = 0; -#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) - set_dacp_server_information(conn); // this will have the effect of telling the scanner that the DACP server is no longer working +#if defined(HAVE_DBUS) || defined(HAVE_MPRIS) + set_dacp_server_information(conn); // this will have the effect of telling the scanner + // that the DACP server is no longer working #endif } } @@ -385,11 +386,10 @@ static void avahi_unregister(void) { int avahi_dacp_monitor(rtsp_conn_info *conn) { dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct)); - if (dbs == NULL) die("can not allocate a dacp_browser_struct."); - + conn->mdns_private_pointer = (void *)dbs; // create the threaded poll code diff --git a/mpris-service.c b/mpris-service.c index e2be908a..4b1a0cff 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -12,44 +12,6 @@ #include "dacp.h" #include "mpris-service.h" -int send_simple_dacp_command(const char *command) { - int reply = 0; - if (playing_conn) { - char server_reply[2000]; - debug(1, "Sending command \"%s\".", command); - ssize_t reply_size = - dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply)); - if (reply_size >= 0) { - // not interested in the response. - // if (strstr(server_reply, "HTTP/1.1 204") == server_reply) { - // debug(1,"Client response is No Content"); - // } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) { - // debug("Client response is OK, with content"); - // } else { - - if (strstr(server_reply, "HTTP/1.1 204") != server_reply) { - debug(1, - "Client request to server responded with %d characters starting with this response:", - strlen(server_reply)); - int i; - for (i = 0; i < reply_size; i++) - if (server_reply[i] < ' ') - debug(1, "%d %02x", i, server_reply[i]); - else - debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]); - // sprintf((char *)message + 2 * i, "%02x", server_reply[i]); - // debug(1,"Content is \"%s\".",message); - } - } else { - debug(1, "Error at rtp_send_client_command"); - reply = -1; - } - } else { - debug(1, "no thread playing -- RemoteCommand ignored."); - reply = -1; - } - return reply; -} static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, gpointer user_data) { @@ -59,7 +21,7 @@ static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocati } static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, - gpointer user_data) { + gpointer user_data) { send_simple_dacp_command("previtem"); media_player2_player_complete_previous(skeleton, invocation); return TRUE; @@ -133,7 +95,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam NULL); g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL); g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL); - g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL); + g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), + NULL); debug(1, "Shairport Sync D-BUS service started on interface \"%s\".", name); diff --git a/player.c b/player.c index 8530f444..d3be8577 100644 --- a/player.c +++ b/player.c @@ -1579,7 +1579,7 @@ static void *player_thread_func(void *arg) { // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the // port number - //mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private); + // mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private); mdns_dacp_monitor(conn); conn->framesProcessedInThisEpoch = 0; @@ -1623,11 +1623,11 @@ static void *player_thread_func(void *arg) { } } - // set the default volume to whaterver it was before, as stored in the config airplay_volume - debug(1,"Set initial volume to %f.",config.airplay_volume); - - player_volume(config.airplay_volume,conn); - + // set the default volume to whaterver it was before, as stored in the config airplay_volume + debug(1, "Set initial volume to %f.", config.airplay_volume); + + player_volume(config.airplay_volume, conn); + uint64_t tens_of_seconds = 0; while (!conn->player_thread_please_stop) { abuf_t *inframe = buffer_get_frame(conn); @@ -2193,8 +2193,8 @@ static void *player_thread_func(void *arg) { // stop watching for DACP port number stuff mdns_dacp_dont_monitor(conn); // begin looking out for information about the client - // as a remote control. Specifically we might need - // the port number + // as a remote control. Specifically we might need + // the port number if (config.output->stop) config.output->stop(); @@ -2468,8 +2468,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } #endif - // here, store the volume for possible use in the future - config.airplay_volume = airplay_volume; + // here, store the volume for possible use in the future + config.airplay_volume = airplay_volume; } void player_volume(double airplay_volume, rtsp_conn_info *conn) { diff --git a/player.h b/player.h index 3c9f43d0..0f557299 100644 --- a/player.h +++ b/player.h @@ -62,8 +62,9 @@ typedef struct { } stream_cfg; typedef struct { - int connection_number; // for debug ID purposes, nothing else... - int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less right. Odd. + int connection_number; // for debug ID purposes, nothing else... + int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less + // right. Odd. #if defined(HAVE_DBUS) || defined(HAVE_MPRIS) enum session_status_type play_state; #endif diff --git a/rtp.c b/rtp.c index 22646d79..ef16f94a 100644 --- a/rtp.c +++ b/rtp.c @@ -227,10 +227,13 @@ void *rtp_control_receiver(void *arg) { sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn); if (config.use_negotiated_latencies) { - int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection; + int64_t la = + sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection; if (la != config.latency) { config.latency = la; - debug(1,"Using negotiated latency of %lld frames and a static latency correction of %lld",sync_rtp_timestamp - rtp_timestamp_less_latency,conn->staticLatencyCorrection); + //debug(1, + // "Using negotiated latency of %lld frames and a static latency correction of %lld", + // sync_rtp_timestamp - rtp_timestamp_less_latency, conn->staticLatencyCorrection); } } diff --git a/rtsp.c b/rtsp.c index 2f613934..7e816726 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1,7 +1,7 @@ /* * RTSP protocol handler. This file is part of Shairport Sync * Copyright (c) James Laird 2013 - + * Modifications associated with audio synchronization, mutithreading and * metadata handling copyright (c) Mike Brady 2014-2017 * All rights reserved. @@ -373,22 +373,22 @@ static void debug_print_msg_headers(int level, rtsp_message *msg) { static void debug_print_msg_content(int level, rtsp_message *msg) { if (msg->contentlength) { - char *obf = malloc(msg->contentlength*2+1); + char *obf = malloc(msg->contentlength * 2 + 1); if (obf) { - char *obfp = obf; - int obfc; - for (obfc=0;obfccontentlength;obfc++) { - sprintf(obfp,"%02X",msg->content[obfc]); - obfp+=2; - }; - *obfp=0; - debug(level,"Content (hex): \"%s\"",obf); - free(obf); + char *obfp = obf; + int obfc; + for (obfc = 0; obfc < msg->contentlength; obfc++) { + sprintf(obfp, "%02X", msg->content[obfc]); + obfp += 2; + }; + *obfp = 0; + debug(level, "Content (hex): \"%s\"", obf); + free(obf); } else { - debug(level,"Can't allocate space for debug buffer"); + debug(level, "Can't allocate space for debug buffer"); } } else { - debug(level,"No content"); + debug(level, "No content"); } } @@ -635,7 +635,7 @@ static void msg_write_response(int fd, rtsp_message *resp) { } // Here, if there's content, write the Content-Length header ... - + if (resp->contentlength) { debug(1, "Responding with content of length %d", resp->contentlength); n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength); @@ -646,14 +646,14 @@ static void msg_write_response(int fd, rtsp_message *resp) { } int ignore = write(fd, pkt, p - pkt); - + // Here, if there's content, write it if (resp->contentlength) { debug(1, "Content is \"%s\"", resp->content); - ignore = write(fd,resp->content,resp->contentlength); + ignore = write(fd, resp->content, resp->contentlength); } - - ignore = write(fd,"\r\n",strlen("\r\n")); + + ignore = write(fd, "\r\n", strlen("\r\n")); } static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { @@ -739,7 +739,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * // debug(1,"RTSP Flush Requested: %u.",rtptime); #ifdef CONFIG_METADATA if (p) - send_metadata('ssnc', 'flsr', p+1, strlen(p+1), req, 1); + send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); else send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); #endif @@ -1353,19 +1353,20 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1, "Connection %d: GET_PARAMETER", conn->connection_number); - //debug_print_msg_headers(1,req); - //debug_print_msg_content(1,req); - - if ((req->content) && (req->contentlength==strlen("volume\r\n")) && strstr(req->content,"volume")==req->content) { - //debug(1,"Current volume sought"); + // debug_print_msg_headers(1,req); + // debug_print_msg_content(1,req); + + if ((req->content) && (req->contentlength == strlen("volume\r\n")) && + strstr(req->content, "volume") == req->content) { + // debug(1,"Current volume sought"); char *p = malloc(128); // will be automatically deallocated with the response is deleted if (p) { - resp->content=p; - resp->contentlength=sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume); + resp->content = p; + resp->contentlength = sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume); } else { - debug(1,"Couldn't allocate space for a response."); + debug(1, "Couldn't allocate space for a response."); } - } + } resp->respcode = 200; } diff --git a/shairport.c b/shairport.c index effc4cca..6ba0fba5 100644 --- a/shairport.c +++ b/shairport.c @@ -440,8 +440,9 @@ int parse_options(int argc, char **argv) { config.resyncthreshold = 1.0 * fResyncthreshold / 44100; config.tolerance = 1.0 * fTolerance / 44100; config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set - config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if nothing else comes in first. - + config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if + // nothing else comes in first. + config_setting_t *setting; const char *str = 0; int value = 0; @@ -1540,7 +1541,7 @@ int main(int argc, char **argv) { #endif #if defined(HAVE_DBUS) || defined(HAVE_MPRIS) - debug(1,"Requesting DACP Monitor"); + debug(1, "Requesting DACP Monitor"); dacp_monitor_start(); #endif diff --git a/tinyhttp/LICENSE b/tinyhttp/LICENSE new file mode 100644 index 00000000..ba420590 --- /dev/null +++ b/tinyhttp/LICENSE @@ -0,0 +1,23 @@ +Copyright 2012 Matthew Endsley +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted providing that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/tinyhttp/README.md b/tinyhttp/README.md new file mode 100644 index 00000000..367138f7 --- /dev/null +++ b/tinyhttp/README.md @@ -0,0 +1,28 @@ +tinyhttp +======== +Tiny (as in minimal) implementation of an HTTP response parser. The parser +itself is only dependent on: +* ` - tolower` +* ` - memcpy` + +For more information please see my blog post at: + +[![Build Status](https://api.travis-ci.org/mendsley/tinyhttp.png)](http://travis-ci.org/mendsley/tinyhttp) + +Contact +------- +[@MatthewEndsley](https://twitter.com/#!/MatthewEndsley) + + +License +------- +Copyright 2012 Matthew Endsley + +This project is governed by the BSD 2-clause license. For details see the file +titled LICENSE in the project root folder. + +Compiling +--------- +`gcc -c *.c && g++ -std=c++0x example.cpp *.o -o example` + +`./example` will fetch the root of diff --git a/tinyhttp/chunk.c b/tinyhttp/chunk.c new file mode 100644 index 00000000..3f7c889e --- /dev/null +++ b/tinyhttp/chunk.c @@ -0,0 +1,79 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +static const unsigned char http_chunk_state[] = { +/* * LF CR HEX */ + 0xC1, 0xC1, 0xC1, 1, /* s0: initial hex char */ + 0xC1, 0xC1, 2, 0x81, /* s1: additional hex chars, followed by CR */ + 0xC1, 0x83, 0xC1, 0xC1, /* s2: trailing LF */ + 0xC1, 0xC1, 4, 0xC1, /* s3: CR after chunk block */ + 0xC1, 0xC0, 0xC1, 0xC1, /* s4: LF after chunk block */ +}; + +int http_parse_chunked(int* state, int *size, char ch) +{ + int newstate, code = 0; + switch (ch) { + case '\n': code = 1; break; + case '\r': code = 2; break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': case 'a': case 'b': + case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': code = 3; break; + } + + newstate = http_chunk_state[*state * 4 + code]; + *state = (newstate & 0xF); + + switch (newstate) { + case 0xC0: + return *size != 0; + + case 0xC1: /* error */ + *size = -1; + return 0; + + case 0x01: /* initial char */ + *size = 0; + /* fallthrough */ + case 0x81: /* size char */ + if (ch >= 'a') + *size = *size * 16 + (ch - 'a' + 10); + else if (ch >= 'A') + *size = *size * 16 + (ch - 'A' + 10); + else + *size = *size * 16 + (ch - '0'); + break; + + case 0x83: + return *size == 0; + } + + return 1; +} + diff --git a/tinyhttp/chunk.h b/tinyhttp/chunk.h new file mode 100644 index 00000000..9736e836 --- /dev/null +++ b/tinyhttp/chunk.h @@ -0,0 +1,48 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HTTP_CHUNK_H +#define HTTP_CHUNK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Parses the size out of a chunk-encoded HTTP response. Returns non-zero if it + * needs more data. Retuns zero success or error. When error: size == -1 On + * success, size = size of following chunk data excluding trailing \r\n. User is + * expected to process or otherwise seek past chunk data up to the trailing + * \r\n. The state parameter is used for internal state and should be + * initialized to zero the first call. + */ +int http_parse_chunked(int* state, int *size, char ch); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/tinyhttp/example.cpp b/tinyhttp/example.cpp new file mode 100644 index 00000000..4054f9a7 --- /dev/null +++ b/tinyhttp/example.cpp @@ -0,0 +1,190 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +/* +Compiling example: +$ g++ -o example example.cpp +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "http.h" + +// directly embed the source here +extern "C" { + #include "http.c" + #include "header.c" + #include "chunk.c" +} + +// return a socket connected to a hostname, or -1 +int connectsocket(const char* host, int port) +{ + + addrinfo* result = NULL; + sockaddr_in addr = {0}; + int s; + + if (getaddrinfo(host, NULL, NULL, &result)) + goto error; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + for (addrinfo* ai = result; ai != NULL; ai = ai->ai_next) { + if (ai->ai_family != AF_INET) + continue; + + const sockaddr_in *ai_in = (const sockaddr_in*)ai->ai_addr; + addr.sin_addr = ai_in->sin_addr; + break; + } + + freeaddrinfo(result); + + if (addr.sin_addr.s_addr == INADDR_ANY) + goto error; + + s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) + goto error; + + if (connect(s, (const sockaddr*)&addr, sizeof(addr))) + goto error; + + return s; + +error: + if (s != -1) + close(s); + if (result) + freeaddrinfo(result); + return -1; +} + +// Response data/funcs +struct HttpResponse { + std::vector body; + int code; +}; + +static void* response_realloc(void* opaque, void* ptr, int size) +{ + return realloc(ptr, size); +} + +static void response_body(void* opaque, const char* data, int size) +{ + HttpResponse* response = (HttpResponse*)opaque; + response->body.insert(response->body.end(), data, data + size); +} + +static void response_header(void* opaque, const char* ckey, int nkey, const char* cvalue, int nvalue) +{ /* example doesn't care about headers */ } + +static void response_code(void* opaque, int code) +{ + HttpResponse* response = (HttpResponse*)opaque; + response->code = code; +} + +static const http_funcs responseFuncs = { + response_realloc, + response_body, + response_header, + response_code, +}; + +int main() +{ + + int conn = connectsocket("nothings.org", 80); + if (conn < 0) { + fprintf(stderr, "Failed to connect socket\n"); + return -1; + } + + const char request[] = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"; + int len = send(conn, request, sizeof(request) - 1, 0); + if (len != sizeof(request) - 1) { + fprintf(stderr, "Failed to send request\n"); + close(conn); + return -1; + } + + HttpResponse response; + response.code = 0; + + http_roundtripper rt; + http_init(&rt, responseFuncs, &response); + + bool needmore = true; + char buffer[1024]; + while (needmore) { + const char* data = buffer; + int ndata = recv(conn, buffer, sizeof(buffer), 0); + if (ndata <= 0) { + fprintf(stderr, "Error receiving data\n"); + http_free(&rt); + close(conn); + return -1; + } + + while (needmore && ndata) { + int read; + needmore = http_data(&rt, data, ndata, &read); + ndata -= read; + data += read; + } + } + + if (http_iserror(&rt)) { + fprintf(stderr, "Error parsing data\n"); + http_free(&rt); + close(conn); + return -1; + } + + http_free(&rt); + close(conn); + + printf("Response: %d\n", response.code); + if (!response.body.empty()) { + printf("%s\n", &response.body[0]); + } + + return 0; +} diff --git a/tinyhttp/header.c b/tinyhttp/header.c new file mode 100644 index 00000000..cda9eb54 --- /dev/null +++ b/tinyhttp/header.c @@ -0,0 +1,72 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "header.h" + +static unsigned char http_header_state[] = { +/* * \t \n \r ' ' , : PAD */ + 0x80, 1, 0xC1, 0xC1, 1, 0x80, 0x80, 0xC1, /* state 0: HTTP version */ + 0x81, 2, 0xC1, 0xC1, 2, 1, 1, 0xC1, /* state 1: Response code */ + 0x82, 0x82, 4, 3, 0x82, 0x82, 0x82, 0xC1, /* state 2: Response reason */ + 0xC1, 0xC1, 4, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 3: HTTP version newline */ + 0x84, 0xC1, 0xC0, 5, 0xC1, 0xC1, 6, 0xC1, /* state 4: Start of header field */ + 0xC1, 0xC1, 0xC0, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 5: Last CR before end of header */ + 0x87, 6, 0xC1, 0xC1, 6, 0x87, 0x87, 0xC1, /* state 6: leading whitespace before header value */ + 0x87, 0x87, 0xC4, 10, 0x87, 0x88, 0x87, 0xC1, /* state 7: header field value */ + 0x87, 0x88, 6, 9, 0x88, 0x88, 0x87, 0xC1, /* state 8: Split value field value */ + 0xC1, 0xC1, 6, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 9: CR after split value field */ + 0xC1, 0xC1, 0xC4, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, /* state 10:CR after header value */ +}; + +int http_parse_header_char(int* state, char ch) +{ + int newstate, code = 0; + switch (ch) { + case '\t': code = 1; break; + case '\n': code = 2; break; + case '\r': code = 3; break; + case ' ': code = 4; break; + case ',': code = 5; break; + case ':': code = 6; break; + } + + newstate = http_header_state[*state * 8 + code]; + *state = (newstate & 0xF); + + switch (newstate) { + case 0xC0: return http_header_status_done; + case 0xC1: return http_header_status_done; + case 0xC4: return http_header_status_store_keyvalue; + case 0x80: return http_header_status_version_character; + case 0x81: return http_header_status_code_character; + case 0x82: return http_header_status_status_character; + case 0x84: return http_header_status_key_character; + case 0x87: return http_header_status_value_character; + case 0x88: return http_header_status_value_character; + } + + return http_header_status_continue; +} diff --git a/tinyhttp/header.h b/tinyhttp/header.h new file mode 100644 index 00000000..7f8ee6ab --- /dev/null +++ b/tinyhttp/header.h @@ -0,0 +1,61 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HTTP_HEADER_H +#define HTTP_HEADER_H + +#if defined(__cplusplus) +extern "C" { +#endif + +enum http_header_status +{ + http_header_status_done, + http_header_status_continue, + http_header_status_version_character, + http_header_status_code_character, + http_header_status_status_character, + http_header_status_key_character, + http_header_status_value_character, + http_header_status_store_keyvalue +}; + +/** + * Parses a single character of an HTTP header stream. The state parameter is + * used as internal state and should be initialized to zero for the first call. + * Return value is a value from the http_header_status enuemeration specifying + * the semantics of the character. If an error is encountered, + * http_header_status_done will be returned with a non-zero state parameter. On + * success http_header_status_done is returned with the state parameter set to + * zero. + */ +int http_parse_header_char(int* state, char ch); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/tinyhttp/http.c b/tinyhttp/http.c new file mode 100644 index 00000000..40eb1c79 --- /dev/null +++ b/tinyhttp/http.c @@ -0,0 +1,227 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "http.h" + +#include +#include + +#include "header.h" +#include "chunk.h" + +static void append_body(struct http_roundtripper* rt, const char* data, int ndata) +{ + rt->funcs.body(rt->opaque, data, ndata); +} + +static void grow_scratch(struct http_roundtripper* rt, int size) +{ + if (rt->nscratch >= size) + return; + + if (size < 64) + size = 64; + int nsize = (rt->nscratch * 3) / 2; + if (nsize < size) + nsize = size; + + rt->scratch = (char*)rt->funcs.realloc_scratch(rt->opaque, rt->scratch, nsize); + rt->nscratch = nsize; +} + +static int min(int a, int b) +{ + return a > b ? b : a; +} + +enum http_roundtripper_state { + http_roundtripper_header, + http_roundtripper_chunk_header, + http_roundtripper_chunk_data, + http_roundtripper_raw_data, + http_roundtripper_unknown_data, + http_roundtripper_close, + http_roundtripper_error, +}; + +void http_init(struct http_roundtripper* rt, struct http_funcs funcs, void* opaque) +{ + rt->funcs = funcs; + rt->scratch = 0; + rt->opaque = opaque; + rt->code = 0; + rt->parsestate = 0; + rt->contentlength = -1; + rt->state = http_roundtripper_header; + rt->nscratch = 0; + rt->nkey = 0; + rt->nvalue = 0; + rt->chunked = 0; +} + +void http_free(struct http_roundtripper* rt) +{ + if (rt->scratch) { + rt->funcs.realloc_scratch(rt->opaque, rt->scratch, 0); + rt->scratch = 0; + } +} + +int http_data(struct http_roundtripper* rt, const char* data, int size, int* read) +{ + const int initial_size = size; + while (size) { + switch (rt->state) { + case http_roundtripper_header: + switch (http_parse_header_char(&rt->parsestate, *data)) { + case http_header_status_done: + rt->funcs.code(rt->opaque, rt->code); + if (rt->parsestate != 0) + rt->state = http_roundtripper_error; + else if (rt->chunked) { + rt->contentlength = 0; + rt->state = http_roundtripper_chunk_header; + } else if (rt->contentlength == 0) + rt->state = http_roundtripper_close; + else if (rt->contentlength > 0) + rt->state = http_roundtripper_raw_data; + else if (rt->contentlength == -1) + rt->state = http_roundtripper_unknown_data; + else + rt->state = http_roundtripper_error; + break; + + case http_header_status_code_character: + rt->code = rt->code * 10 + *data - '0'; + break; + + case http_header_status_key_character: + grow_scratch(rt, rt->nkey + 1); + rt->scratch[rt->nkey] = tolower(*data); + ++rt->nkey; + break; + + case http_header_status_value_character: + grow_scratch(rt, rt->nkey + rt->nvalue + 1); + rt->scratch[rt->nkey+rt->nvalue] = *data; + ++rt->nvalue; + break; + + case http_header_status_store_keyvalue: + if (rt->nkey == 17 && 0 == strncmp(rt->scratch, "transfer-encoding", rt->nkey)) + rt->chunked = (rt->nvalue == 7 && 0 == strncmp(rt->scratch + rt->nkey, "chunked", rt->nvalue)); + else if (rt->nkey == 14 && 0 == strncmp(rt->scratch, "content-length", rt->nkey)) { + int ii, end; + rt->contentlength = 0; + for (ii = rt->nkey, end = rt->nkey + rt->nvalue; ii != end; ++ii) + rt->contentlength = rt->contentlength * 10 + rt->scratch[ii] - '0'; + } + + rt->funcs.header(rt->opaque, rt->scratch, rt->nkey, rt->scratch + rt->nkey, rt->nvalue); + + rt->nkey = 0; + rt->nvalue = 0; + break; + } + + --size; + ++data; + break; + + case http_roundtripper_chunk_header: + if (!http_parse_chunked(&rt->parsestate, &rt->contentlength, *data)) { + if (rt->contentlength == -1) + rt->state = http_roundtripper_error; + else if (rt->contentlength == 0) + rt->state = http_roundtripper_close; + else + rt->state = http_roundtripper_chunk_data; + } + + --size; + ++data; + break; + + case http_roundtripper_chunk_data: { + const int chunksize = min(size, rt->contentlength); + append_body(rt, data, chunksize); + rt->contentlength -= chunksize; + size -= chunksize; + data += chunksize; + + if (rt->contentlength == 0) { + rt->contentlength = 1; + rt->state = http_roundtripper_chunk_header; + } + } + break; + + case http_roundtripper_raw_data: { + const int chunksize = min(size, rt->contentlength); + append_body(rt, data, chunksize); + rt->contentlength -= chunksize; + size -= chunksize; + data += chunksize; + + if (rt->contentlength == 0) + rt->state = http_roundtripper_close; + } + break; + + case http_roundtripper_unknown_data: { + if (size == 0) + rt->state = http_roundtripper_close; + else { + append_body(rt, data, size); + size -= size; + data += size; + } + } + break; + + case http_roundtripper_close: + case http_roundtripper_error: + break; + } + + if (rt->state == http_roundtripper_error || rt->state == http_roundtripper_close) { + if (rt->scratch) { + rt->funcs.realloc_scratch(rt->opaque, rt->scratch, 0); + rt->scratch = 0; + } + *read = initial_size - size; + return 0; + } + } + + *read = initial_size - size; + return 1; +} + +int http_iserror(struct http_roundtripper* rt) +{ + return rt->state == http_roundtripper_error; +} diff --git a/tinyhttp/http.h b/tinyhttp/http.h new file mode 100644 index 00000000..ba7e4cfb --- /dev/null +++ b/tinyhttp/http.h @@ -0,0 +1,93 @@ +/*- + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HTTP_HTTP_H +#define HTTP_HTTP_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Callbacks for handling response data. + * realloc_scratch - reallocate memory, cannot fail. There will only + * be one scratch buffer. Implemnentation may take + * advantage of this fact. + * body - handle HTTP response body data + * header - handle an HTTP header key/value pair + * code - handle the HTTP status code for the response + */ +struct http_funcs { + void* (*realloc_scratch)(void* opaque, void* ptr, int size); + void (*body)(void* opaque, const char* data, int size); + void (*header)(void* opaque, const char* key, int nkey, const char* value, int nvalue); + void (*code)(void* opqaue, int code); +}; + +struct http_roundtripper { + struct http_funcs funcs; + void *opaque; + char *scratch; + int code; + int parsestate; + int contentlength; + int state; + int nscratch; + int nkey; + int nvalue; + int chunked; +}; + +/** + * Initializes a rountripper with the specified response functions. This must + * be called before the rt object is used. + */ +void http_init(struct http_roundtripper* rt, struct http_funcs, void* opaque); + +/** + * Frees any scratch memory allocated during parsing. + */ +void http_free(struct http_roundtripper* rt); + +/** + * Parses a block of HTTP response data. Returns zero if the parser reached the + * end of the response, or an error was encountered. Use http_iserror to check + * for the presence of an error. Returns non-zero if more data is required for + * the response. + */ +int http_data(struct http_roundtripper* rt, const char* data, int size, int* read); + +/** + * Returns non-zero if a completed parser encounted an error. If http_data did + * not return non-zero, the results of this function are undefined. + */ +int http_iserror(struct http_roundtripper* rt); + +#if defined(__cplusplus) +} +#endif + +#endif