/** * 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 #include #include #include #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); }