Add tinyhttp code for sending and reading responses. Clean up some dacp routines. Most new and modified rooutines untested.

This commit is contained in:
Mike Brady
2017-12-18 18:50:01 +00:00
parent 845c4689a8
commit 1637a79dd6
25 changed files with 1231 additions and 425 deletions
+5
View File
@@ -8,6 +8,11 @@ tinysvcmdns.c, tinysvcmdns.h:
All rights reserved.
see tinysvcmdns.[ch] for full license text
tinyhttp
Copyright 2012 Matthew Endsley
All rights reserved
see the tinyhttp/LICENSE for full license text
Shairport:
Copyright (c) 2011-2013 James Laird <jhl@mafipulation.org>
+1 -1
View File
@@ -88,7 +88,7 @@ dbus-interface.c: org.gnome.ShairportSync.xml
endif
if USE_DBUS_CORE_AND_DACP
shairport_sync_SOURCES += dacp.c
shairport_sync_SOURCES += dacp.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
endif
if USE_MPRIS
+12 -8
View File
@@ -97,14 +97,14 @@ static int init(int argc, char **argv) {
// Get a mainloop and its context
mainloop = pa_threaded_mainloop_new();
if (mainloop==NULL)
if (mainloop == NULL)
die("could not create a pa_threaded_mainloop.");
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
if (config.pa_application_name)
context = pa_context_new(mainloop_api, config.pa_application_name);
else
context = pa_context_new(mainloop_api, "Shairport Sync");
if (context==NULL)
if (context == NULL)
die("could not create a new context for pulseaudio.");
// Set a callback so we can wait for the context to be ready
pa_context_set_state_callback(context, &context_state_cb, mainloop);
@@ -116,14 +116,15 @@ static int init(int argc, char **argv) {
if (pa_threaded_mainloop_start(mainloop) != 0)
die("could not start the pulseaudio threaded mainloop");
if (pa_context_connect(context, NULL, 0, NULL) != 0)
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
// Wait for the context to be ready
for (;;) {
pa_context_state_t context_state = pa_context_get_state(context);
if (!PA_CONTEXT_IS_GOOD(context_state))
die("pa context is not good -- the error message \"%s\".",pa_strerror(pa_context_errno(context)));
die("pa context is not good -- the error message \"%s\".",
pa_strerror(pa_context_errno(context)));
if (context_state == PA_CONTEXT_READY)
break;
pa_threaded_mainloop_wait(mainloop);
@@ -176,13 +177,16 @@ static void start(int sample_rate, int sample_format) {
// Connect stream to the default audio output sink
if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0)
die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
// Wait for the stream to be ready
for (;;) {
pa_stream_state_t stream_state = pa_stream_get_state(stream);
if(!PA_STREAM_IS_GOOD(stream_state))
die("stream state is no longer good while waiting for stream to become ready -- the error message is \"%s\".",pa_strerror(pa_context_errno(context)));
if (!PA_STREAM_IS_GOOD(stream_state))
die("stream state is no longer good while waiting for stream to become ready -- the error "
"message is \"%s\".",
pa_strerror(pa_context_errno(context)));
if (stream_state == PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(mainloop);
+1 -1
View File
@@ -82,7 +82,7 @@ enum sps_format_t {
typedef struct {
config_t *cfg;
double airplay_volume; // stored here for reloading when necessary
char *appName; // normally the app is called shairport-syn, but it may be symlinked
char *appName; // normally the app is called shairport-syn, but it may be symlinked
char *password;
char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running
// on host %h"
+324 -286
View File
@@ -39,6 +39,8 @@
#include <time.h>
#include <unistd.h>
#include "tinyhttp/http.h"
typedef struct {
uint16_t port;
short connection_family; // AF_INET6 or AF_INET
@@ -50,12 +52,58 @@ typedef struct {
pthread_t dacp_monitor_thread;
dacp_server_record dacp_server;
// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
struct HttpResponse {
void *body; // this will be a malloc'ed pointer
ssize_t malloced_size; // this will be its allocated size
ssize_t size; // the current size of the content
int code;
};
static void *response_realloc(void *opaque, void *ptr, int size) { return realloc(ptr, size); }
static void response_body(void *opaque, const char *data, int size) {
struct HttpResponse *response = (struct HttpResponse *)opaque;
ssize_t space_available = response->malloced_size - response->size;
if (space_available < size) {
printf("Getting more space for the response -- need %d bytes but only %d bytes left.\n", size,
size - space_available);
ssize_t size_requested = size - space_available + response->malloced_size + 16384;
void *t = realloc(response->body, size_requested);
response->malloced_size = size_requested;
if (t)
response->body = t;
else {
printf("Can't allocate any more space for parser.\n");
exit(-1);
}
}
memcpy(response->body + response->size, data, size);
response->size += size;
}
static void response_header(void *opaque, const char *ckey, int nkey, const char *cvalue,
int nvalue) { /* example doesn't care about headers */
}
static void response_code(void *opaque, int code) {
struct HttpResponse *response = (struct HttpResponse *)opaque;
response->code = code;
}
static const struct http_funcs responseFuncs = {
response_realloc, response_body, response_header, response_code,
};
static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
ssize_t dacp_send_command(const char *command, char *response, size_t max_response_length) {
ssize_t reply_size = -1;
int dacp_send_command(const char *command, char **body, size_t *bodysize) {
// will malloc space for the body or set it to NULL -- the caller should free it.
// try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
// to do it.
struct timespec mutex_wait_time;
@@ -65,20 +113,19 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon
struct addrinfo hints, *res;
int sockfd;
char message[20000], server_reply[2000], portstring[10], server[256];
memset(&message, 0, sizeof(message));
if ((response) && (max_response_length))
memset(response, 0, max_response_length);
else
memset(&server_reply, 0, sizeof(server_reply));
memset(&portstring, 0, sizeof(portstring));
struct HttpResponse response;
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
response.code = 400; // client error
char portstring[10], server[256], message[1024];
memset(&portstring, 0, sizeof(portstring));
if (dacp_server.connection_family == AF_INET6) {
sprintf(server, "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
} else {
strcpy(server, dacp_server.ip_string);
}
sprintf(portstring, "%u", dacp_server.port);
// first, load up address structs with getaddrinfo():
@@ -115,28 +162,74 @@ ssize_t dacp_send_command(const char *command, char *response, size_t max_respon
// Send command
if (send(sockfd, message, strlen(message), 0) < 0) {
if (send(sockfd, message, strlen(message), 0) != strlen(message)) {
debug(1, "Send failed");
}
} else {
// Receive a reply from the server
if ((response) && (max_response_length))
reply_size = recv(sockfd, response, max_response_length, 0);
else
reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
if (reply_size < 0) {
debug(1, "recv failed");
response.body = malloc(2048); // it can resize this if necessary
response.malloced_size = 2048;
struct http_roundtripper rt;
http_init(&rt, responseFuncs, &response);
int needmore = 1;
int looperror = 0;
char buffer[1024];
while (needmore && !looperror) {
const char *data = buffer;
int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
if (ndata <= 0) {
debug(1, "Error receiving data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
looperror = 1;
}
while (needmore && ndata && !looperror) {
int read;
needmore = http_data(&rt, data, ndata, &read);
ndata -= read;
data += read;
}
}
if (http_iserror(&rt)) {
debug(1, "Error parsing data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
}
http_free(&rt);
close(sockfd);
}
close(sockfd);
}
}
pthread_mutex_unlock(&dacp_conversation_lock);
} else {
debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
// debug(1, "Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
response.code = 408; // not strictly correct
}
return reply_size;
*body = response.body;
*bodysize = response.size;
return response.code;
}
int send_simple_dacp_command(const char *command) {
int reply = 0;
char *server_reply = NULL;
debug(1, "Sending command \"%s\".", command);
ssize_t reply_size = 0;
reply = dacp_send_command(command, &server_reply, &reply_size);
if (server_reply) {
free(server_reply);
server_reply = NULL;
}
return reply;
}
// this will be running on the thread of its caller, not of the conversation thread...
void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conversation thread that
@@ -155,9 +248,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) { // tell the DACP conver
void *dacp_monitor_thread_code(void *na) {
int scan_index = 0;
char server_reply[2000];
char server_reply[10000];
debug(1, "DACP monitor thread started.");
// wait until we get a valid port number to begin monitoring it
int32_t revision_number = 1;
while (1) {
pthread_mutex_lock(&dacp_server_information_lock);
while (dacp_server.port == 0) {
@@ -165,37 +259,114 @@ void *dacp_monitor_thread_code(void *na) {
pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
}
pthread_mutex_unlock(&dacp_server_information_lock);
//debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
// dacp_server.ip_string, dacp_server.port, scan_index++);
ssize_t le;
char *response = NULL;
int32_t item_size;
char command[1024] = "";
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d", revision_number);
// debug(1,"Command: \"%s\"",command);
int result = dacp_send_command(command, &response, &le);
if (result == 200) {
char *sp = response;
if (le >= 0) {
// here start looking for the contents of the status update
if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
sp -= item_size; // drop down into the array -- don't skip over it
le -= 8;
char typestring[5];
while (le >= 8) {
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
*(uint32_t *)typestring = htonl(type);
typestring[4] = 0;
printf("\"%s\" %4d", typestring, item_size);
le -= item_size + 8;
char *t;
char u;
char *st;
int32_t r;
uint64_t s, v;
int i;
switch (type) {
case 'mstt':
case 'cant':
case 'cast':
case 'cmmk':
case 'caas':
case 'caar':
case 'astm':
t = sp - item_size;
r = ntohl(*(int32_t *)(t));
printf(" %d", r);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t);
t++;
}
printf(")");
break;
case 'cmsr':
t = sp - item_size;
revision_number = ntohl(*(int32_t *)(t));
printf(" Serial Number: %d", revision_number);
break;
case 'cann':
case 'cana':
case 'canl':
case 'cang':
t = sp - item_size;
st = strndup(t, item_size);
printf(" \"%s\"", st);
free(st);
break;
case 'asai':
t = sp - item_size;
s = ntohl(*(uint32_t *)(t));
s = s << 32;
t += 4;
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
s += v;
printf(" %llu", s);
printf(" (0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t);
t++;
}
printf(")");
break;
default:
printf(" 0x");
t = sp - item_size;
for (i = 0; i < item_size; i++) {
printf("%02x", *t);
t++;
}
break;
}
printf("\n");
}
} else {
printf("Status Update not found.\n");
}
ssize_t reply_size =
dacp_send_command("playstatusupdate", server_reply, sizeof(server_reply));
if (reply_size >= 0) {
// not interested in the response.
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
debug(1,"Client response is No Content");
} else if (strstr(server_reply, "HTTP/1.1 501") == server_reply) {
debug(1,"Client response is \"Not Implemented\". DACP Server has disconnnected.");
dacp_server.port = 0; // stop scanning until the next time it's used
/*
} else {
debug(1,
"Client request to server responded with %d characters starting with this response:",
strlen(server_reply));
int i;
for (i = 0; i < reply_size; i++)
if (server_reply[i] < ' ')
debug(1, "%d %02x", i, server_reply[i]);
else
debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]);
// sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
// debug(1,"Content is \"%s\".",message);
*/
debug(1, "Can't find any content in playerstatusupdate request");
}
} else {
debug(1, "Error at rtp_send_client_command");
if (result != 403)
debug(1, "Unexpected response %d to playerstatusupdate request", result);
}
sleep(3);
if (response) {
free(response);
response = NULL;
};
sleep(1);
}
debug(1, "DACP monitor thread exiting.");
pthread_exit(NULL);
@@ -218,278 +389,145 @@ uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
return type;
}
ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
size_t max_response_length) {
ssize_t reply_size = -1;
// try to do this transaction on the DACP server, but don't wait for more than 20 ms to be allowed
// to do it.
struct timespec mutex_wait_time;
mutex_wait_time.tv_sec = 0;
mutex_wait_time.tv_nsec = 20000000; // 20 ms
if (conn->rtp_running) {
if (conn->dacp_port == 0) {
debug(1, "Can't send a remote request: no valid active remote.");
} else {
struct addrinfo hints, *res;
int sockfd;
char message[20000], server_reply[2000], portstring[10], server[256];
memset(&message, 0, sizeof(message));
if ((response) && (max_response_length))
memset(response, 0, max_response_length);
else
memset(&server_reply, 0, sizeof(server_reply));
memset(&portstring, 0, sizeof(portstring));
if (conn->connection_ip_family == AF_INET6) {
sprintf(server, "%s%%%u", conn->client_ip_string, conn->self_scope_id);
} else {
strcpy(server, conn->client_ip_string);
}
sprintf(portstring, "%u", conn->dacp_port);
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
getaddrinfo(server, portstring, &hints, &res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
int mutex_reply = pthread_mutex_timedlock(&dacp_conversation_lock, &mutex_wait_time);
if (mutex_reply == 0) {
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
debug(1, "Could not create socket");
} else {
// connect!
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
debug(1, "connect failed. Error");
} else {
sprintf(message,
"GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
command, conn->client_ip_string, conn->dacp_port, conn->dacp_active_remote);
// Send command
if (send(sockfd, message, strlen(message), 0) < 0) {
debug(1, "Send failed");
}
// Receive a reply from the server
if ((response) && (max_response_length))
reply_size = recv(sockfd, response, max_response_length, 0);
else
reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
if (reply_size < 0) {
debug(1, "recv failed");
}
close(sockfd);
}
}
pthread_mutex_unlock(&dacp_conversation_lock);
} else {
debug(1,
"Could not acquire a lock on the dacp transmit/receive section. Possible timeout?");
}
}
} else {
debug(1, "Request to pause non-existent play stream -- ignored.");
}
return reply_size;
}
int32_t dacp_get_client_volume(rtsp_conn_info *conn) {
char server_reply[2000];
char *server_reply = NULL;
int32_t overall_volume = -1;
ssize_t reply_size = dacp_send_client_command(conn, "getproperty?properties=dmcp.volume",
server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
char *sp = strstr(server_reply, "Content-Length: ");
if (sp) { // there is something there
sp += strlen("Content-Length: ");
int le = atoi(sp);
if (le == 32) {
sp = strstr(sp, "\r") + 4 + 28;
uint32_t *np = (uint32_t *)sp;
overall_volume = ntohl(*np);
// debug(1,"Overall Volume is %d.",overall_volume);
} else {
debug(1, "Can't find the volume tag");
}
} else {
debug(1, "Can't find any content in volume control request");
}
} else {
debug(1, "Unexpected response to dacp volume control request");
}
ssize_t reply_size;
int response =
dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
if (response == 200) { // if we get an okay
uint32_t *np = (uint32_t *)server_reply;
overall_volume = ntohl(*np);
// debug(1,"Overall Volume is %d.",overall_volume);
free(server_reply);
} else {
debug(1, "Error asking for dacp volume.");
debug(1, "Unexpected response %d to dacp volume control request", response);
}
return overall_volume;
}
int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
char server_reply[2000];
int reply = -1; // will bve fixed if there is no problem
char message[1000];
memset(message, 0, sizeof(message));
sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
// debug(1,"sending \"%s\"",message);
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"dacp_set_include_speaker_volume successful.");
reply = 0;
}
} else {
debug(1, "dacp_set_include_speaker_volume unsuccessful.");
}
return reply;
return send_simple_dacp_command(message);
// should return 204
}
int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
char server_reply[2000];
int reply = -1; // will bve fixed if there is no problem
char message[1000];
memset(message, 0, sizeof(message));
sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
// debug(1,"sending \"%s\"",message);
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"dacp_set_speaker_volume successful.");
reply = 0;
}
} else {
debug(1, "dacp_set_speaker_volume unsuccessful.");
}
return reply;
return send_simple_dacp_command(message);
// should return 204
}
int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
int max_size_of_array) {
char server_reply[2000];
char *server_reply = NULL;
int speaker_index = -1; // will be incremented before use
int reply = -1; // will bve fixed if there is no problem
ssize_t reply_size =
dacp_send_client_command(conn, "getspeakers", server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
char *sp = strstr(server_reply, "Content-Length: ");
if (sp) { // there is something there
sp += strlen("Content-Length: ");
int32_t le = atoi(sp);
int32_t item_size = 0;
sp = strstr(sp, "\r") + 4;
if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
// debug(1,"Speakers:",item_size);
sp -= item_size; // drop down into the array -- don't skip over it
le -= 8;
while (le >= 8) {
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
// debug(1,">>>> Dictionary:");
sp -= item_size;
le -= 8;
speaker_index++;
if (speaker_index == max_size_of_array)
return -1; // too many speakers
speaker_info[speaker_index].active = 0;
speaker_info[speaker_index].speaker_number = 0;
speaker_info[speaker_index].volume = 0;
speaker_info[speaker_index].name = NULL;
} else {
le -= item_size + 8;
char *t;
char u;
int32_t r;
int64_t s, v;
switch (type) {
case 'minm':
t = sp - item_size;
speaker_info[speaker_index].name = strndup(t, item_size);
// debug(1," \"%s\"",speaker_info[speaker_index].name);
break;
/*
case 'cads':
t = sp-item_size;
r = ntohl(*(int32_t*)(t));
//debug(1,"CADS: \"%d\".",r);
break;
*/
case 'cmvo':
t = sp - item_size;
r = ntohl(*(int32_t *)(t));
speaker_info[speaker_index].volume = r;
// debug(1,"Volume: \"%d\".",r);
break;
case 'msma':
t = sp - item_size;
s = ntohl(*(uint32_t *)(t));
s = s << 32;
t += 4;
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
s += v;
speaker_info[speaker_index].speaker_number = s;
// debug(1,"Speaker machine number: %ld",s);
break;
ssize_t le;
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;
}
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;
}
+6 -6
View File
@@ -1,15 +1,14 @@
#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;
@@ -22,12 +21,13 @@ void dacp_monitor_start();
uint32_t dacp_tlv_crawl(
char **p,
int32_t *length); // return the code of the next TLV entity and advance the pointer beyond it.
ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
size_t max_response_length);
int32_t dacp_get_client_volume(rtsp_conn_info *conn); // return the overall volume from the client
int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_array,
int max_size_of_array);
void set_dacp_server_information(rtsp_conn_info* conn); // tell the DACP conversation thread that the dacp server information has been set or changed
void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that
// the dacp server information has been set
// or changed
int send_simple_dacp_command(const char *command);
+1 -31
View File
@@ -142,37 +142,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gpointer user_data) {
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
const gchar *command, gpointer user_data) {
debug(1, "RemoteCommand with command \"%s\".", command);
if (playing_conn) {
char server_reply[2000];
ssize_t reply_size =
dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
// not interested in the response.
// if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"Client response is No Content");
// } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) {
// debug("Client response is OK, with content");
// } else {
if (strstr(server_reply, "HTTP/1.1 204") != server_reply) {
debug(1,
"Client request to server responded with %d characters starting with this response:",
strlen(server_reply));
int i;
for (i = 0; i < reply_size; i++)
if (server_reply[i] < ' ')
debug(1, "%d %02x", i, server_reply[i]);
else
debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]);
// sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
// debug(1,"Content is \"%s\".",message);
}
} else {
debug(1, "Error at rtp_send_client_command");
}
} else {
debug(1, "no thread playing -- RemoteCommand ignored.");
}
send_simple_dacp_command(const char *command);
shairport_sync_complete_remote_command(skeleton, invocation);
return TRUE;
}
-1
View File
@@ -33,5 +33,4 @@
#define SAFAMILY sa_family
#endif
#endif // _DEFINITIONS_H
+1 -1
View File
@@ -2,8 +2,8 @@
#define _MDNS_H
#include "config.h"
#include <stdint.h>
#include <player.h>
#include <stdint.h>
extern int mdns_pid;
+5 -5
View File
@@ -69,7 +69,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
const char *host_name, const AvahiAddress *address, uint16_t port,
AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) {
assert(r);
rtsp_conn_info *conn = (rtsp_conn_info *)userdata;
dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
@@ -141,8 +141,9 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
if (conn->dacp_id != 0) {
debug(1, "Client's DACP status withdrawn.");
conn->dacp_port = 0;
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
set_dacp_server_information(conn); // this will have the effect of telling the scanner that the DACP server is no longer working
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
set_dacp_server_information(conn); // this will have the effect of telling the scanner
// that the DACP server is no longer working
#endif
}
}
@@ -385,11 +386,10 @@ static void avahi_unregister(void) {
int avahi_dacp_monitor(rtsp_conn_info *conn) {
dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
if (dbs == NULL)
die("can not allocate a dacp_browser_struct.");
conn->mdns_private_pointer = (void *)dbs;
// create the threaded poll code
+3 -40
View File
@@ -12,44 +12,6 @@
#include "dacp.h"
#include "mpris-service.h"
int send_simple_dacp_command(const char *command) {
int reply = 0;
if (playing_conn) {
char server_reply[2000];
debug(1, "Sending command \"%s\".", command);
ssize_t reply_size =
dacp_send_client_command(playing_conn, command, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
// not interested in the response.
// if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"Client response is No Content");
// } else if (strstr(server_reply, "HTTP/1.1 200 OK") != server_reply) {
// debug("Client response is OK, with content");
// } else {
if (strstr(server_reply, "HTTP/1.1 204") != server_reply) {
debug(1,
"Client request to server responded with %d characters starting with this response:",
strlen(server_reply));
int i;
for (i = 0; i < reply_size; i++)
if (server_reply[i] < ' ')
debug(1, "%d %02x", i, server_reply[i]);
else
debug(1, "%d %02x '%c'", i, server_reply[i], server_reply[i]);
// sprintf((char *)message + 2 * i, "%02x", server_reply[i]);
// debug(1,"Content is \"%s\".",message);
}
} else {
debug(1, "Error at rtp_send_client_command");
reply = -1;
}
} else {
debug(1, "no thread playing -- RemoteCommand ignored.");
reply = -1;
}
return reply;
}
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
gpointer user_data) {
@@ -59,7 +21,7 @@ static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocati
}
static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
gpointer user_data) {
gpointer user_data) {
send_simple_dacp_command("previtem");
media_player2_player_complete_previous(skeleton, invocation);
return TRUE;
@@ -133,7 +95,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
NULL);
debug(1, "Shairport Sync D-BUS service started on interface \"%s\".", name);
+10 -10
View File
@@ -1579,7 +1579,7 @@ static void *player_thread_func(void *arg) {
// start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the
// port number
//mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private);
// mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private);
mdns_dacp_monitor(conn);
conn->framesProcessedInThisEpoch = 0;
@@ -1623,11 +1623,11 @@ static void *player_thread_func(void *arg) {
}
}
// set the default volume to whaterver it was before, as stored in the config airplay_volume
debug(1,"Set initial volume to %f.",config.airplay_volume);
player_volume(config.airplay_volume,conn);
// set the default volume to whaterver it was before, as stored in the config airplay_volume
debug(1, "Set initial volume to %f.", config.airplay_volume);
player_volume(config.airplay_volume, conn);
uint64_t tens_of_seconds = 0;
while (!conn->player_thread_please_stop) {
abuf_t *inframe = buffer_get_frame(conn);
@@ -2193,8 +2193,8 @@ static void *player_thread_func(void *arg) {
// stop watching for DACP port number stuff
mdns_dacp_dont_monitor(conn); // begin looking out for information about the client
// as a remote control. Specifically we might need
// the port number
// as a remote control. Specifically we might need
// the port number
if (config.output->stop)
config.output->stop();
@@ -2468,8 +2468,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
}
#endif
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
}
void player_volume(double airplay_volume, rtsp_conn_info *conn) {
+3 -2
View File
@@ -62,8 +62,9 @@ typedef struct {
} stream_cfg;
typedef struct {
int connection_number; // for debug ID purposes, nothing else...
int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less right. Odd.
int connection_number; // for debug ID purposes, nothing else...
int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less
// right. Odd.
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
enum session_status_type play_state;
#endif
+5 -2
View File
@@ -227,10 +227,13 @@ void *rtp_control_receiver(void *arg) {
sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
if (config.use_negotiated_latencies) {
int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
int64_t la =
sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
if (la != config.latency) {
config.latency = la;
debug(1,"Using negotiated latency of %lld frames and a static latency correction of %lld",sync_rtp_timestamp - rtp_timestamp_less_latency,conn->staticLatencyCorrection);
//debug(1,
// "Using negotiated latency of %lld frames and a static latency correction of %lld",
// sync_rtp_timestamp - rtp_timestamp_less_latency, conn->staticLatencyCorrection);
}
}
+29 -28
View File
@@ -1,7 +1,7 @@
/*
* RTSP protocol handler. This file is part of Shairport Sync
* Copyright (c) James Laird 2013
* Modifications associated with audio synchronization, mutithreading and
* metadata handling copyright (c) Mike Brady 2014-2017
* All rights reserved.
@@ -373,22 +373,22 @@ static void debug_print_msg_headers(int level, rtsp_message *msg) {
static void debug_print_msg_content(int level, rtsp_message *msg) {
if (msg->contentlength) {
char *obf = malloc(msg->contentlength*2+1);
char *obf = malloc(msg->contentlength * 2 + 1);
if (obf) {
char *obfp = obf;
int obfc;
for (obfc=0;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");
}
}
@@ -635,7 +635,7 @@ static void msg_write_response(int fd, rtsp_message *resp) {
}
// Here, if there's content, write the Content-Length header ...
if (resp->contentlength) {
debug(1, "Responding with content of length %d", resp->contentlength);
n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength);
@@ -646,14 +646,14 @@ static void msg_write_response(int fd, rtsp_message *resp) {
}
int ignore = write(fd, pkt, p - pkt);
// Here, if there's content, write it
if (resp->contentlength) {
debug(1, "Content is \"%s\"", resp->content);
ignore = write(fd,resp->content,resp->contentlength);
ignore = write(fd, resp->content, resp->contentlength);
}
ignore = write(fd,"\r\n",strlen("\r\n"));
ignore = write(fd, "\r\n", strlen("\r\n"));
}
static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
@@ -739,7 +739,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
// debug(1,"RTSP Flush Requested: %u.",rtptime);
#ifdef CONFIG_METADATA
if (p)
send_metadata('ssnc', 'flsr', p+1, strlen(p+1), req, 1);
send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
else
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
#endif
@@ -1353,19 +1353,20 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re
static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
// debug(1, "Connection %d: GET_PARAMETER", conn->connection_number);
//debug_print_msg_headers(1,req);
//debug_print_msg_content(1,req);
if ((req->content) && (req->contentlength==strlen("volume\r\n")) && strstr(req->content,"volume")==req->content) {
//debug(1,"Current volume sought");
// debug_print_msg_headers(1,req);
// debug_print_msg_content(1,req);
if ((req->content) && (req->contentlength == strlen("volume\r\n")) &&
strstr(req->content, "volume") == req->content) {
// debug(1,"Current volume sought");
char *p = malloc(128); // will be automatically deallocated with the response is deleted
if (p) {
resp->content=p;
resp->contentlength=sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
resp->content = p;
resp->contentlength = sprintf(p, "\r\nvolume: %.6f\r\n", config.airplay_volume);
} else {
debug(1,"Couldn't allocate space for a response.");
debug(1, "Couldn't allocate space for a response.");
}
}
}
resp->respcode = 200;
}
+4 -3
View File
@@ -440,8 +440,9 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if nothing else comes in first.
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
// nothing else comes in first.
config_setting_t *setting;
const char *str = 0;
int value = 0;
@@ -1540,7 +1541,7 @@ int main(int argc, char **argv) {
#endif
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
debug(1,"Requesting DACP Monitor");
debug(1, "Requesting DACP Monitor");
dacp_monitor_start();
#endif
+23
View File
@@ -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.
+28
View File
@@ -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>
[![Build Status](https://api.travis-ci.org/mendsley/tinyhttp.png)](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>
+79
View File
@@ -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;
}
+48
View File
@@ -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
+190
View File
@@ -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;
}
+72
View File
@@ -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;
}
+61
View File
@@ -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
View File
@@ -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;
}
+93
View File
@@ -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