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 <jhl@mafipulation.org>
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
// 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);
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);
// 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);
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"
#include <time.h>
#include <unistd.h>
+#include "tinyhttp/http.h"
+
typedef struct {
uint16_t port;
short connection_family; // AF_INET6 or AF_INET
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;
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():
// 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
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) {
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);
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<le;i++) {
- if (*sp < ' ')
- debug(1,"%d %02x", i, *sp);
- else
- debug(1,"%d %02x '%c'", i, *sp,*sp);
- sp++;
- }
- */
-
+ // 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, "Can't find any content in dacp speakers request");
+ debug(1, "Speaker array not found.");
}
+ /*
+ int i;
+ for (i=0;i<le;i++) {
+ if (*sp < ' ')
+ debug(1,"%d %02x", i, *sp);
+ else
+ debug(1,"%d %02x '%c'", i, *sp,*sp);
+ sp++;
+ }
+ */
} else {
- debug(1, "Unexpected response to dacp speakers request");
+ debug(1, "Can't find any content in dacp speakers request");
}
+ free(server_reply);
+ server_reply = NULL;
} else {
- debug(1, "Error asking for dacp speakers.");
+ debug(1, "Unexpected response %d to dacp speakers request", response);
}
return reply;
}
#pragma once
#include "common.h"
#include "config.h"
-#include <sys/socket.h>
#include <pthread.h>
+#include <sys/socket.h>
#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;
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);
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;
}
#define SAFAMILY sa_family
#endif
-
#endif // _DEFINITIONS_H
#define _MDNS_H
#include "config.h"
-#include <stdint.h>
#include <player.h>
+#include <stdint.h>
extern int mdns_pid;
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;
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
}
}
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
#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) {
}
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;
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);
// 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;
}
}
- // 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);
// 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();
}
#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) {
} 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
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);
}
}
/*
* 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.
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;obfc<msg->contentlength;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");
}
}
}
// 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);
}
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) {
// 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
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;
}
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;
#endif
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
- debug(1,"Requesting DACP Monitor");
+ debug(1, "Requesting DACP Monitor");
dacp_monitor_start();
#endif
--- /dev/null
+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.
--- /dev/null
+tinyhttp
+========
+Tiny (as in minimal) implementation of an HTTP response parser. The parser
+itself is only dependent on:
+* `<ctype.h> - tolower`
+* `<string.h> - memcpy`
+
+For more information please see my blog post at: <http://mendsley.github.com/2012/12/19/tinyhttp.html>
+
+[](http://travis-ci.org/mendsley/tinyhttp)
+
+Contact
+-------
+[@MatthewEndsley](https://twitter.com/#!/MatthewEndsley)
+<https://github.com/mendsley/tinyhttp>
+
+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 <http://nothings.org>
--- /dev/null
+/*-
+ * 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;
+}
+
--- /dev/null
+/*-
+ * 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
--- /dev/null
+/*-
+ * 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 <string>
+#include <vector>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#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<char> 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;
+}
--- /dev/null
+/*-
+ * 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;
+}
--- /dev/null
+/*-
+ * 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
--- /dev/null
+/*-
+ * 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 <ctype.h>
+#include <string.h>
+
+#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;
+}
--- /dev/null
+/*-
+ * 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