Add tinyhttp code for sending and reading responses. Clean up some dacp routines. Most new and modified rooutines untested.
This commit is contained in:
@@ -8,6 +8,11 @@ tinysvcmdns.c, tinysvcmdns.h:
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
see tinysvcmdns.[ch] for full license text
|
see tinysvcmdns.[ch] for full license text
|
||||||
|
|
||||||
|
tinyhttp
|
||||||
|
Copyright 2012 Matthew Endsley
|
||||||
|
All rights reserved
|
||||||
|
see the tinyhttp/LICENSE for full license text
|
||||||
|
|
||||||
Shairport:
|
Shairport:
|
||||||
Copyright (c) 2011-2013 James Laird <jhl@mafipulation.org>
|
Copyright (c) 2011-2013 James Laird <jhl@mafipulation.org>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -88,7 +88,7 @@ dbus-interface.c: org.gnome.ShairportSync.xml
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if USE_DBUS_CORE_AND_DACP
|
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
|
endif
|
||||||
|
|
||||||
if USE_MPRIS
|
if USE_MPRIS
|
||||||
|
|||||||
+12
-8
@@ -97,14 +97,14 @@ static int init(int argc, char **argv) {
|
|||||||
|
|
||||||
// Get a mainloop and its context
|
// Get a mainloop and its context
|
||||||
mainloop = pa_threaded_mainloop_new();
|
mainloop = pa_threaded_mainloop_new();
|
||||||
if (mainloop==NULL)
|
if (mainloop == NULL)
|
||||||
die("could not create a pa_threaded_mainloop.");
|
die("could not create a pa_threaded_mainloop.");
|
||||||
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
|
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
|
||||||
if (config.pa_application_name)
|
if (config.pa_application_name)
|
||||||
context = pa_context_new(mainloop_api, config.pa_application_name);
|
context = pa_context_new(mainloop_api, config.pa_application_name);
|
||||||
else
|
else
|
||||||
context = pa_context_new(mainloop_api, "Shairport Sync");
|
context = pa_context_new(mainloop_api, "Shairport Sync");
|
||||||
if (context==NULL)
|
if (context == NULL)
|
||||||
die("could not create a new context for pulseaudio.");
|
die("could not create a new context for pulseaudio.");
|
||||||
// Set a callback so we can wait for the context to be ready
|
// Set a callback so we can wait for the context to be ready
|
||||||
pa_context_set_state_callback(context, &context_state_cb, mainloop);
|
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)
|
if (pa_threaded_mainloop_start(mainloop) != 0)
|
||||||
die("could not start the pulseaudio threaded mainloop");
|
die("could not start the pulseaudio threaded mainloop");
|
||||||
if (pa_context_connect(context, NULL, 0, NULL) != 0)
|
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
|
// Wait for the context to be ready
|
||||||
for (;;) {
|
for (;;) {
|
||||||
pa_context_state_t context_state = pa_context_get_state(context);
|
pa_context_state_t context_state = pa_context_get_state(context);
|
||||||
if (!PA_CONTEXT_IS_GOOD(context_state))
|
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)
|
if (context_state == PA_CONTEXT_READY)
|
||||||
break;
|
break;
|
||||||
pa_threaded_mainloop_wait(mainloop);
|
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
|
// Connect stream to the default audio output sink
|
||||||
if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0)
|
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
|
// Wait for the stream to be ready
|
||||||
for (;;) {
|
for (;;) {
|
||||||
pa_stream_state_t stream_state = pa_stream_get_state(stream);
|
pa_stream_state_t stream_state = pa_stream_get_state(stream);
|
||||||
if(!PA_STREAM_IS_GOOD(stream_state))
|
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)));
|
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)
|
if (stream_state == PA_STREAM_READY)
|
||||||
break;
|
break;
|
||||||
pa_threaded_mainloop_wait(mainloop);
|
pa_threaded_mainloop_wait(mainloop);
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "tinyhttp/http.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
short connection_family; // AF_INET6 or AF_INET
|
short connection_family; // AF_INET6 or AF_INET
|
||||||
@@ -50,12 +52,58 @@ typedef struct {
|
|||||||
pthread_t dacp_monitor_thread;
|
pthread_t dacp_monitor_thread;
|
||||||
dacp_server_record dacp_server;
|
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_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static pthread_mutex_t dacp_server_information_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;
|
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) {
|
int dacp_send_command(const char *command, char **body, size_t *bodysize) {
|
||||||
ssize_t reply_size = -1;
|
|
||||||
|
// 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
|
// try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
|
||||||
// to do it.
|
// to do it.
|
||||||
struct timespec mutex_wait_time;
|
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;
|
struct addrinfo hints, *res;
|
||||||
int sockfd;
|
int sockfd;
|
||||||
|
|
||||||
char message[20000], server_reply[2000], portstring[10], server[256];
|
struct HttpResponse response;
|
||||||
memset(&message, 0, sizeof(message));
|
response.body = NULL;
|
||||||
if ((response) && (max_response_length))
|
response.malloced_size = 0;
|
||||||
memset(response, 0, max_response_length);
|
response.size = 0;
|
||||||
else
|
response.code = 400; // client error
|
||||||
memset(&server_reply, 0, sizeof(server_reply));
|
|
||||||
memset(&portstring, 0, sizeof(portstring));
|
|
||||||
|
|
||||||
|
char portstring[10], server[256], message[1024];
|
||||||
|
memset(&portstring, 0, sizeof(portstring));
|
||||||
if (dacp_server.connection_family == AF_INET6) {
|
if (dacp_server.connection_family == AF_INET6) {
|
||||||
sprintf(server, "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
|
sprintf(server, "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
|
||||||
} else {
|
} else {
|
||||||
strcpy(server, dacp_server.ip_string);
|
strcpy(server, dacp_server.ip_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(portstring, "%u", dacp_server.port);
|
sprintf(portstring, "%u", dacp_server.port);
|
||||||
|
|
||||||
// first, load up address structs with getaddrinfo():
|
// 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
|
// Send command
|
||||||
|
|
||||||
if (send(sockfd, message, strlen(message), 0) < 0) {
|
if (send(sockfd, message, strlen(message), 0) != strlen(message)) {
|
||||||
debug(1, "Send failed");
|
debug(1, "Send failed");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive a reply from the server
|
while (needmore && ndata && !looperror) {
|
||||||
if ((response) && (max_response_length))
|
int read;
|
||||||
reply_size = recv(sockfd, response, max_response_length, 0);
|
needmore = http_data(&rt, data, ndata, &read);
|
||||||
else
|
ndata -= read;
|
||||||
reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
|
data += read;
|
||||||
if (reply_size < 0) {
|
|
||||||
debug(1, "recv failed");
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
pthread_mutex_unlock(&dacp_conversation_lock);
|
||||||
} else {
|
} 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...
|
// 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 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) {
|
void *dacp_monitor_thread_code(void *na) {
|
||||||
int scan_index = 0;
|
int scan_index = 0;
|
||||||
char server_reply[2000];
|
char server_reply[10000];
|
||||||
debug(1, "DACP monitor thread started.");
|
debug(1, "DACP monitor thread started.");
|
||||||
// wait until we get a valid port number to begin monitoring it
|
// wait until we get a valid port number to begin monitoring it
|
||||||
|
int32_t revision_number = 1;
|
||||||
while (1) {
|
while (1) {
|
||||||
pthread_mutex_lock(&dacp_server_information_lock);
|
pthread_mutex_lock(&dacp_server_information_lock);
|
||||||
while (dacp_server.port == 0) {
|
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_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&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++);
|
// 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);
|
||||||
|
|
||||||
ssize_t reply_size =
|
*(uint32_t *)typestring = htonl(type);
|
||||||
dacp_send_command("playstatusupdate", server_reply, sizeof(server_reply));
|
typestring[4] = 0;
|
||||||
if (reply_size >= 0) {
|
printf("\"%s\" %4d", typestring, item_size);
|
||||||
// not interested in the response.
|
|
||||||
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
|
le -= item_size + 8;
|
||||||
debug(1,"Client response is No Content");
|
char *t;
|
||||||
} else if (strstr(server_reply, "HTTP/1.1 501") == server_reply) {
|
char u;
|
||||||
debug(1,"Client response is \"Not Implemented\". DACP Server has disconnnected.");
|
char *st;
|
||||||
dacp_server.port = 0; // stop scanning until the next time it's used
|
int32_t r;
|
||||||
/*
|
uint64_t s, v;
|
||||||
} else {
|
|
||||||
debug(1,
|
|
||||||
"Client request to server responded with %d characters starting with this response:",
|
|
||||||
strlen(server_reply));
|
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < reply_size; i++)
|
switch (type) {
|
||||||
if (server_reply[i] < ' ')
|
|
||||||
debug(1, "%d %02x", i, server_reply[i]);
|
case 'mstt':
|
||||||
else
|
case 'cant':
|
||||||
debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]);
|
case 'cast':
|
||||||
// sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
|
case 'cmmk':
|
||||||
// debug(1,"Content is \"%s\".",message);
|
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 {
|
} else {
|
||||||
debug(1, "Error at rtp_send_client_command");
|
printf("Status Update not found.\n");
|
||||||
}
|
}
|
||||||
sleep(3);
|
|
||||||
|
} else {
|
||||||
|
debug(1, "Can't find any content in playerstatusupdate request");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (result != 403)
|
||||||
|
debug(1, "Unexpected response %d to playerstatusupdate request", result);
|
||||||
|
}
|
||||||
|
if (response) {
|
||||||
|
free(response);
|
||||||
|
response = NULL;
|
||||||
|
};
|
||||||
|
sleep(1);
|
||||||
}
|
}
|
||||||
debug(1, "DACP monitor thread exiting.");
|
debug(1, "DACP monitor thread exiting.");
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
@@ -218,184 +389,53 @@ uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
|
|||||||
return type;
|
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) {
|
int32_t dacp_get_client_volume(rtsp_conn_info *conn) {
|
||||||
char server_reply[2000];
|
char *server_reply = NULL;
|
||||||
int32_t overall_volume = -1;
|
int32_t overall_volume = -1;
|
||||||
|
ssize_t reply_size;
|
||||||
ssize_t reply_size = dacp_send_client_command(conn, "getproperty?properties=dmcp.volume",
|
int response =
|
||||||
server_reply, sizeof(server_reply));
|
dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
|
||||||
if (reply_size >= 0) {
|
if (response == 200) { // if we get an okay
|
||||||
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
|
uint32_t *np = (uint32_t *)server_reply;
|
||||||
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);
|
overall_volume = ntohl(*np);
|
||||||
// debug(1,"Overall Volume is %d.",overall_volume);
|
// debug(1,"Overall Volume is %d.",overall_volume);
|
||||||
|
free(server_reply);
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Can't find the volume tag");
|
debug(1, "Unexpected response %d to dacp volume control request", response);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug(1, "Can't find any content in volume control request");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug(1, "Unexpected response to dacp volume control request");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug(1, "Error asking for dacp volume.");
|
|
||||||
}
|
}
|
||||||
return overall_volume;
|
return overall_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
|
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];
|
char message[1000];
|
||||||
memset(message, 0, sizeof(message));
|
memset(message, 0, sizeof(message));
|
||||||
sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
|
sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
|
||||||
// debug(1,"sending \"%s\"",message);
|
// debug(1,"sending \"%s\"",message);
|
||||||
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
|
return send_simple_dacp_command(message);
|
||||||
if (reply_size >= 0) {
|
// should return 204
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int dacp_set_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) {
|
||||||
char server_reply[2000];
|
|
||||||
int reply = -1; // will bve fixed if there is no problem
|
|
||||||
char message[1000];
|
char message[1000];
|
||||||
memset(message, 0, sizeof(message));
|
memset(message, 0, sizeof(message));
|
||||||
sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
|
sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
|
||||||
// debug(1,"sending \"%s\"",message);
|
// debug(1,"sending \"%s\"",message);
|
||||||
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
|
return send_simple_dacp_command(message);
|
||||||
if (reply_size >= 0) {
|
// should return 204
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
|
int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
|
||||||
int max_size_of_array) {
|
int max_size_of_array) {
|
||||||
char server_reply[2000];
|
char *server_reply = NULL;
|
||||||
int speaker_index = -1; // will be incremented before use
|
int speaker_index = -1; // will be incremented before use
|
||||||
int reply = -1; // will bve fixed if there is no problem
|
int reply = -1; // will bve fixed if there is no problem
|
||||||
ssize_t reply_size =
|
ssize_t le;
|
||||||
dacp_send_client_command(conn, "getspeakers", server_reply, sizeof(server_reply));
|
|
||||||
if (reply_size >= 0) {
|
int response = dacp_send_command("getspeakers", &server_reply, &le);
|
||||||
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
|
if (response == 200) {
|
||||||
char *sp = strstr(server_reply, "Content-Length: ");
|
char *sp = server_reply;
|
||||||
if (sp) { // there is something there
|
int32_t item_size;
|
||||||
sp += strlen("Content-Length: ");
|
if (le >= 8) {
|
||||||
int32_t le = atoi(sp);
|
|
||||||
int32_t item_size = 0;
|
|
||||||
sp = strstr(sp, "\r") + 4;
|
|
||||||
if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
|
if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
|
||||||
// debug(1,"Speakers:",item_size);
|
// debug(1,"Speakers:",item_size);
|
||||||
sp -= item_size; // drop down into the array -- don't skip over it
|
sp -= item_size; // drop down into the array -- don't skip over it
|
||||||
@@ -481,15 +521,13 @@ int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
|
|||||||
sp++;
|
sp++;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Can't find any content in dacp speakers request");
|
debug(1, "Can't find any content in dacp speakers request");
|
||||||
}
|
}
|
||||||
|
free(server_reply);
|
||||||
|
server_reply = NULL;
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Unexpected response to dacp speakers request");
|
debug(1, "Unexpected response %d to dacp speakers request", response);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug(1, "Error asking for dacp speakers.");
|
|
||||||
}
|
}
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
static pthread_mutex_t dacp_server_information_lock;
|
static pthread_mutex_t dacp_server_information_lock;
|
||||||
static pthread_cond_t dacp_server_information_cv;
|
static pthread_cond_t dacp_server_information_cv;
|
||||||
|
|
||||||
|
|
||||||
typedef struct dacp_speaker_stuff {
|
typedef struct dacp_speaker_stuff {
|
||||||
int64_t speaker_number;
|
int64_t speaker_number;
|
||||||
int active;
|
int active;
|
||||||
@@ -22,12 +21,13 @@ void dacp_monitor_start();
|
|||||||
uint32_t dacp_tlv_crawl(
|
uint32_t dacp_tlv_crawl(
|
||||||
char **p,
|
char **p,
|
||||||
int32_t *length); // return the code of the next TLV entity and advance the pointer beyond it.
|
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
|
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_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_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 dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_array,
|
||||||
int max_size_of_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);
|
||||||
|
|||||||
+1
-31
@@ -142,37 +142,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gpointer user_data) {
|
|||||||
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
||||||
const gchar *command, gpointer user_data) {
|
const gchar *command, gpointer user_data) {
|
||||||
debug(1, "RemoteCommand with command \"%s\".", command);
|
debug(1, "RemoteCommand with command \"%s\".", command);
|
||||||
if (playing_conn) {
|
send_simple_dacp_command(const char *command);
|
||||||
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.");
|
|
||||||
}
|
|
||||||
shairport_sync_complete_remote_command(skeleton, invocation);
|
shairport_sync_complete_remote_command(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,5 +33,4 @@
|
|||||||
#define SAFAMILY sa_family
|
#define SAFAMILY sa_family
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#endif // _DEFINITIONS_H
|
#endif // _DEFINITIONS_H
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
#define _MDNS_H
|
#define _MDNS_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <stdint.h>
|
|
||||||
#include <player.h>
|
#include <player.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
extern int mdns_pid;
|
extern int mdns_pid;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -142,7 +142,8 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
|||||||
debug(1, "Client's DACP status withdrawn.");
|
debug(1, "Client's DACP status withdrawn.");
|
||||||
conn->dacp_port = 0;
|
conn->dacp_port = 0;
|
||||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
#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
|
set_dacp_server_information(conn); // this will have the effect of telling the scanner
|
||||||
|
// that the DACP server is no longer working
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +387,6 @@ int avahi_dacp_monitor(rtsp_conn_info *conn) {
|
|||||||
|
|
||||||
dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
|
dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
|
||||||
|
|
||||||
|
|
||||||
if (dbs == NULL)
|
if (dbs == NULL)
|
||||||
die("can not allocate a dacp_browser_struct.");
|
die("can not allocate a dacp_browser_struct.");
|
||||||
|
|
||||||
|
|||||||
+2
-39
@@ -12,44 +12,6 @@
|
|||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
|
|
||||||
#include "mpris-service.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,
|
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
gpointer user_data) {
|
gpointer user_data) {
|
||||||
@@ -133,7 +95,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
|
|||||||
NULL);
|
NULL);
|
||||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), 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-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);
|
debug(1, "Shairport Sync D-BUS service started on interface \"%s\".", name);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the
|
||||||
// port number
|
// 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);
|
mdns_dacp_monitor(conn);
|
||||||
|
|
||||||
conn->framesProcessedInThisEpoch = 0;
|
conn->framesProcessedInThisEpoch = 0;
|
||||||
@@ -1624,9 +1624,9 @@ static void *player_thread_func(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set the default volume to whaterver it was before, as stored in the config airplay_volume
|
// 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);
|
debug(1, "Set initial volume to %f.", config.airplay_volume);
|
||||||
|
|
||||||
player_volume(config.airplay_volume,conn);
|
player_volume(config.airplay_volume, conn);
|
||||||
|
|
||||||
uint64_t tens_of_seconds = 0;
|
uint64_t tens_of_seconds = 0;
|
||||||
while (!conn->player_thread_please_stop) {
|
while (!conn->player_thread_please_stop) {
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int connection_number; // for debug ID purposes, nothing else...
|
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.
|
int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less
|
||||||
|
// right. Odd.
|
||||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
||||||
enum session_status_type play_state;
|
enum session_status_type play_state;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -227,10 +227,13 @@ void *rtp_control_receiver(void *arg) {
|
|||||||
sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
|
sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
|
||||||
|
|
||||||
if (config.use_negotiated_latencies) {
|
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) {
|
if (la != config.latency) {
|
||||||
config.latency = la;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
static void debug_print_msg_content(int level, rtsp_message *msg) {
|
||||||
if (msg->contentlength) {
|
if (msg->contentlength) {
|
||||||
char *obf = malloc(msg->contentlength*2+1);
|
char *obf = malloc(msg->contentlength * 2 + 1);
|
||||||
if (obf) {
|
if (obf) {
|
||||||
char *obfp = obf;
|
char *obfp = obf;
|
||||||
int obfc;
|
int obfc;
|
||||||
for (obfc=0;obfc<msg->contentlength;obfc++) {
|
for (obfc = 0; obfc < msg->contentlength; obfc++) {
|
||||||
sprintf(obfp,"%02X",msg->content[obfc]);
|
sprintf(obfp, "%02X", msg->content[obfc]);
|
||||||
obfp+=2;
|
obfp += 2;
|
||||||
};
|
};
|
||||||
*obfp=0;
|
*obfp = 0;
|
||||||
debug(level,"Content (hex): \"%s\"",obf);
|
debug(level, "Content (hex): \"%s\"", obf);
|
||||||
free(obf);
|
free(obf);
|
||||||
} else {
|
} else {
|
||||||
debug(level,"Can't allocate space for debug buffer");
|
debug(level, "Can't allocate space for debug buffer");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug(level,"No content");
|
debug(level, "No content");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,10 +650,10 @@ static void msg_write_response(int fd, rtsp_message *resp) {
|
|||||||
// Here, if there's content, write it
|
// Here, if there's content, write it
|
||||||
if (resp->contentlength) {
|
if (resp->contentlength) {
|
||||||
debug(1, "Content is \"%s\"", resp->content);
|
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) {
|
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);
|
// debug(1,"RTSP Flush Requested: %u.",rtptime);
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
if (p)
|
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
|
else
|
||||||
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
|
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
|
||||||
#endif
|
#endif
|
||||||
@@ -1353,17 +1353,18 @@ 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) {
|
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(1, "Connection %d: GET_PARAMETER", conn->connection_number);
|
||||||
//debug_print_msg_headers(1,req);
|
// debug_print_msg_headers(1,req);
|
||||||
//debug_print_msg_content(1,req);
|
// debug_print_msg_content(1,req);
|
||||||
|
|
||||||
if ((req->content) && (req->contentlength==strlen("volume\r\n")) && strstr(req->content,"volume")==req->content) {
|
if ((req->content) && (req->contentlength == strlen("volume\r\n")) &&
|
||||||
//debug(1,"Current volume sought");
|
strstr(req->content, "volume") == req->content) {
|
||||||
|
// debug(1,"Current volume sought");
|
||||||
char *p = malloc(128); // will be automatically deallocated with the response is deleted
|
char *p = malloc(128); // will be automatically deallocated with the response is deleted
|
||||||
if (p) {
|
if (p) {
|
||||||
resp->content=p;
|
resp->content = p;
|
||||||
resp->contentlength=sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
|
resp->contentlength = sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
|
||||||
} else {
|
} else {
|
||||||
debug(1,"Couldn't allocate space for a response.");
|
debug(1, "Couldn't allocate space for a response.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp->respcode = 200;
|
resp->respcode = 200;
|
||||||
|
|||||||
+3
-2
@@ -440,7 +440,8 @@ int parse_options(int argc, char **argv) {
|
|||||||
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
|
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
|
||||||
config.tolerance = 1.0 * fTolerance / 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.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;
|
config_setting_t *setting;
|
||||||
const char *str = 0;
|
const char *str = 0;
|
||||||
@@ -1540,7 +1541,7 @@ int main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
||||||
debug(1,"Requesting DACP Monitor");
|
debug(1, "Requesting DACP Monitor");
|
||||||
dacp_monitor_start();
|
dacp_monitor_start();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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 <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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+227
@@ -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 <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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user