380 lines
10 KiB
C
Executable File
380 lines
10 KiB
C
Executable File
/**
|
|
* Copyright (C) 2011-2012 Juho Vähä-Herttua
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "raop.h"
|
|
#include "raop_rtp.h"
|
|
#include "raop_rtp.h"
|
|
#include "pairing.h"
|
|
#include "httpd.h"
|
|
|
|
#include "global.h"
|
|
#include "fairplay.h"
|
|
#include "netutils.h"
|
|
#include "logger.h"
|
|
#include "compat.h"
|
|
#include "raop_rtp_mirror.h"
|
|
#include "raop_ntp.h"
|
|
|
|
struct raop_s {
|
|
/* Callbacks for audio and video */
|
|
raop_callbacks_t callbacks;
|
|
|
|
/* Logger instance */
|
|
logger_t *logger;
|
|
|
|
/* Pairing, HTTP daemon and RSA key */
|
|
pairing_t *pairing;
|
|
httpd_t *httpd;
|
|
|
|
dnssd_t *dnssd;
|
|
|
|
unsigned short port;
|
|
};
|
|
|
|
struct raop_conn_s {
|
|
raop_t *raop;
|
|
raop_ntp_t *raop_ntp;
|
|
raop_rtp_t *raop_rtp;
|
|
raop_rtp_mirror_t *raop_rtp_mirror;
|
|
fairplay_t *fairplay;
|
|
pairing_session_t *pairing;
|
|
|
|
unsigned char *local;
|
|
int locallen;
|
|
|
|
unsigned char *remote;
|
|
int remotelen;
|
|
|
|
};
|
|
typedef struct raop_conn_s raop_conn_t;
|
|
|
|
#include "raop_handlers.h"
|
|
|
|
static void *
|
|
conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen) {
|
|
raop_t *raop = opaque;
|
|
raop_conn_t *conn;
|
|
|
|
assert(raop);
|
|
|
|
conn = calloc(1, sizeof(raop_conn_t));
|
|
if (!conn) {
|
|
return NULL;
|
|
}
|
|
conn->raop = raop;
|
|
conn->raop_rtp = NULL;
|
|
conn->raop_ntp = NULL;
|
|
conn->fairplay = fairplay_init(raop->logger);
|
|
|
|
if (!conn->fairplay) {
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
conn->pairing = pairing_session_init(raop->pairing);
|
|
if (!conn->pairing) {
|
|
fairplay_destroy(conn->fairplay);
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
|
|
if (locallen == 4) {
|
|
logger_log(conn->raop->logger, LOGGER_INFO,
|
|
"Local: %d.%d.%d.%d",
|
|
local[0], local[1], local[2], local[3]);
|
|
} else if (locallen == 16) {
|
|
logger_log(conn->raop->logger, LOGGER_INFO,
|
|
"Local: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
|
|
local[0], local[1], local[2], local[3], local[4], local[5], local[6], local[7],
|
|
local[8], local[9], local[10], local[11], local[12], local[13], local[14], local[15]);
|
|
}
|
|
if (remotelen == 4) {
|
|
logger_log(conn->raop->logger, LOGGER_INFO,
|
|
"Remote: %d.%d.%d.%d",
|
|
remote[0], remote[1], remote[2], remote[3]);
|
|
} else if (remotelen == 16) {
|
|
logger_log(conn->raop->logger, LOGGER_INFO,
|
|
"Remote: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
|
|
remote[0], remote[1], remote[2], remote[3], remote[4], remote[5], remote[6], remote[7],
|
|
remote[8], remote[9], remote[10], remote[11], remote[12], remote[13], remote[14], remote[15]);
|
|
}
|
|
|
|
conn->local = malloc(locallen);
|
|
assert(conn->local);
|
|
memcpy(conn->local, local, locallen);
|
|
|
|
conn->remote = malloc(remotelen);
|
|
assert(conn->remote);
|
|
memcpy(conn->remote, remote, remotelen);
|
|
|
|
conn->locallen = locallen;
|
|
conn->remotelen = remotelen;
|
|
|
|
if (raop->callbacks.conn_init) {
|
|
raop->callbacks.conn_init(raop->callbacks.cls);
|
|
}
|
|
|
|
return conn;
|
|
}
|
|
|
|
static void
|
|
conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
|
raop_conn_t *conn = ptr;
|
|
logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request");
|
|
const char *method;
|
|
const char *url;
|
|
const char *cseq;
|
|
|
|
char *response_data = NULL;
|
|
int response_datalen = 0;
|
|
|
|
method = http_request_get_method(request);
|
|
url = http_request_get_url(request);
|
|
cseq = http_request_get_header(request, "CSeq");
|
|
if (!method || !cseq) {
|
|
return;
|
|
}
|
|
|
|
*response = http_response_init("RTSP/1.0", 200, "OK");
|
|
|
|
http_response_add_header(*response, "CSeq", cseq);
|
|
//http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog");
|
|
http_response_add_header(*response, "Server", "AirTunes/220.68");
|
|
|
|
logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url);
|
|
raop_handler_t handler = NULL;
|
|
if (!strcmp(method, "GET") && !strcmp(url, "/info")) {
|
|
handler = &raop_handler_info;
|
|
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup")) {
|
|
handler = &raop_handler_pairsetup;
|
|
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-verify")) {
|
|
handler = &raop_handler_pairverify;
|
|
} else if (!strcmp(method, "POST") && !strcmp(url, "/fp-setup")) {
|
|
handler = &raop_handler_fpsetup;
|
|
} else if (!strcmp(method, "OPTIONS")) {
|
|
handler = &raop_handler_options;
|
|
} else if (!strcmp(method, "SETUP")) {
|
|
handler = &raop_handler_setup;
|
|
} else if (!strcmp(method, "GET_PARAMETER")) {
|
|
handler = &raop_handler_get_parameter;
|
|
} else if (!strcmp(method, "SET_PARAMETER")) {
|
|
handler = &raop_handler_set_parameter;
|
|
} else if (!strcmp(method, "POST") && !strcmp(url, "/feedback")) {
|
|
handler = &raop_handler_feedback;
|
|
} else if (!strcmp(method, "RECORD")) {
|
|
handler = &raop_handler_record;
|
|
} else if (!strcmp(method, "FLUSH")) {
|
|
const char *rtpinfo;
|
|
int next_seq = -1;
|
|
|
|
rtpinfo = http_request_get_header(request, "RTP-Info");
|
|
if (rtpinfo) {
|
|
logger_log(conn->raop->logger, LOGGER_DEBUG, "Flush with RTP-Info: %s", rtpinfo);
|
|
if (!strncmp(rtpinfo, "seq=", 4)) {
|
|
next_seq = strtol(rtpinfo + 4, NULL, 10);
|
|
}
|
|
}
|
|
if (conn->raop_rtp) {
|
|
raop_rtp_flush(conn->raop_rtp, next_seq);
|
|
} else {
|
|
logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at FLUSH");
|
|
}
|
|
} else if (!strcmp(method, "TEARDOWN")) {
|
|
//http_response_add_header(*response, "Connection", "close");
|
|
if (conn->raop_rtp != NULL && raop_rtp_is_running(conn->raop_rtp)) {
|
|
/* Destroy our RTP session */
|
|
raop_rtp_stop(conn->raop_rtp);
|
|
} else if (conn->raop_rtp_mirror) {
|
|
/* Destroy our sessions */
|
|
raop_rtp_destroy(conn->raop_rtp);
|
|
conn->raop_rtp = NULL;
|
|
raop_rtp_mirror_destroy(conn->raop_rtp_mirror);
|
|
conn->raop_rtp_mirror = NULL;
|
|
}
|
|
}
|
|
if (handler != NULL) {
|
|
handler(conn, request, *response, &response_data, &response_datalen);
|
|
}
|
|
http_response_finish(*response, response_data, response_datalen);
|
|
if (response_data) {
|
|
free(response_data);
|
|
response_data = NULL;
|
|
response_datalen = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
conn_destroy(void *ptr) {
|
|
raop_conn_t *conn = ptr;
|
|
|
|
logger_log(conn->raop->logger, LOGGER_INFO, "Destroying connection");
|
|
|
|
if (conn->raop->callbacks.conn_destroy) {
|
|
conn->raop->callbacks.conn_destroy(conn->raop->callbacks.cls);
|
|
}
|
|
|
|
if (conn->raop_ntp) {
|
|
raop_ntp_destroy(conn->raop_ntp);
|
|
}
|
|
if (conn->raop_rtp) {
|
|
/* This is done in case TEARDOWN was not called */
|
|
raop_rtp_destroy(conn->raop_rtp);
|
|
}
|
|
if (conn->raop_rtp_mirror) {
|
|
/* This is done in case TEARDOWN was not called */
|
|
raop_rtp_mirror_destroy(conn->raop_rtp_mirror);
|
|
}
|
|
|
|
conn->raop->callbacks.video_flush(conn->raop->callbacks.cls);
|
|
|
|
free(conn->local);
|
|
free(conn->remote);
|
|
pairing_session_destroy(conn->pairing);
|
|
fairplay_destroy(conn->fairplay);
|
|
free(conn);
|
|
}
|
|
|
|
raop_t *
|
|
raop_init(int max_clients, raop_callbacks_t *callbacks) {
|
|
raop_t *raop;
|
|
pairing_t *pairing;
|
|
httpd_t *httpd;
|
|
httpd_callbacks_t httpd_cbs;
|
|
|
|
assert(callbacks);
|
|
assert(max_clients > 0);
|
|
assert(max_clients < 100);
|
|
|
|
/* Initialize the network */
|
|
if (netutils_init() < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Validate the callbacks structure */
|
|
if (!callbacks->audio_process ||
|
|
!callbacks->video_process) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate the raop_t structure */
|
|
raop = calloc(1, sizeof(raop_t));
|
|
if (!raop) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the logger */
|
|
raop->logger = logger_init();
|
|
pairing = pairing_init_generate();
|
|
if (!pairing) {
|
|
free(raop);
|
|
return NULL;
|
|
}
|
|
|
|
/* Set HTTP callbacks to our handlers */
|
|
memset(&httpd_cbs, 0, sizeof(httpd_cbs));
|
|
httpd_cbs.opaque = raop;
|
|
httpd_cbs.conn_init = &conn_init;
|
|
httpd_cbs.conn_request = &conn_request;
|
|
httpd_cbs.conn_destroy = &conn_destroy;
|
|
|
|
/* Initialize the http daemon */
|
|
httpd = httpd_init(raop->logger, &httpd_cbs, max_clients);
|
|
if (!httpd) {
|
|
pairing_destroy(pairing);
|
|
free(raop);
|
|
return NULL;
|
|
}
|
|
/* Copy callbacks structure */
|
|
memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t));
|
|
raop->pairing = pairing;
|
|
raop->httpd = httpd;
|
|
return raop;
|
|
}
|
|
|
|
void
|
|
raop_destroy(raop_t *raop) {
|
|
if (raop) {
|
|
raop_stop(raop);
|
|
pairing_destroy(raop->pairing);
|
|
httpd_destroy(raop->httpd);
|
|
logger_destroy(raop->logger);
|
|
free(raop);
|
|
|
|
/* Cleanup the network */
|
|
netutils_cleanup();
|
|
}
|
|
}
|
|
|
|
int
|
|
raop_is_running(raop_t *raop) {
|
|
assert(raop);
|
|
|
|
return httpd_is_running(raop->httpd);
|
|
}
|
|
|
|
void
|
|
raop_set_log_level(raop_t *raop, int level) {
|
|
assert(raop);
|
|
|
|
logger_set_level(raop->logger, level);
|
|
}
|
|
|
|
void
|
|
raop_set_port(raop_t *raop, unsigned short port) {
|
|
assert(raop);
|
|
raop->port = port;
|
|
}
|
|
|
|
unsigned short
|
|
raop_get_port(raop_t *raop) {
|
|
assert(raop);
|
|
return raop->port;
|
|
}
|
|
|
|
void *
|
|
raop_get_callback_cls(raop_t *raop) {
|
|
assert(raop);
|
|
return raop->callbacks.cls;
|
|
}
|
|
|
|
void
|
|
raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls) {
|
|
assert(raop);
|
|
|
|
logger_set_callback(raop->logger, callback, cls);
|
|
}
|
|
|
|
void
|
|
raop_set_dnssd(raop_t *raop, dnssd_t *dnssd) {
|
|
assert(dnssd);
|
|
raop->dnssd = dnssd;
|
|
}
|
|
|
|
|
|
int
|
|
raop_start(raop_t *raop, unsigned short *port) {
|
|
assert(raop);
|
|
assert(port);
|
|
return httpd_start(raop->httpd, port);
|
|
}
|
|
|
|
void
|
|
raop_stop(raop_t *raop) {
|
|
assert(raop);
|
|
httpd_stop(raop->httpd);
|
|
} |