From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sat, 12 Nov 2022 17:45:36 +0000 (+0000) Subject: Add OutputRate and OutputFormat properties to the D-Bus interface. X-Git-Tag: 4.1.1~2^2~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9af924dd43bc9664b1f45e5ecd99b81d345d27cf;p=thirdparty%2Fshairport-sync.git Add OutputRate and OutputFormat properties to the D-Bus interface. --- diff --git a/dbus-service.c b/dbus-service.c index 51c1b128..9c0bd718 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -1041,6 +1041,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name #endif shairport_sync_set_service_name(SHAIRPORT_SYNC(shairportSyncSkeleton), config.service_name); + shairport_sync_set_output_rate(SHAIRPORT_SYNC(shairportSyncSkeleton), config.output_rate); + shairport_sync_set_output_format(SHAIRPORT_SYNC(shairportSyncSkeleton), sps_format_description_string(config.output_format)); #ifdef CONFIG_AIRPLAY_2 shairport_sync_set_protocol(SHAIRPORT_SYNC(shairportSyncSkeleton), "AirPlay 2"); diff --git a/new-rtsp.c b/new-rtsp.c deleted file mode 100644 index 8d11fbc7..00000000 --- a/new-rtsp.c +++ /dev/null @@ -1,5564 +0,0 @@ -/* - * RTSP protocol handler. This file is part of Shairport Sync - * Copyright (c) James Laird 2013 - - * Modifications associated with audio synchronization, multithreading and - * metadata handling copyright (c) Mike Brady 2014-2022 - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "activity_monitor.h" -#include "config.h" - -#ifdef CONFIG_OPENSSL -#include -#endif - -#ifdef CONFIG_MBEDTLS -#include -#include -#endif - -#ifdef CONFIG_POLARSSL -#include -#endif - -#include "common.h" -#include "player.h" -#include "rtp.h" -#include "rtsp.h" - -#ifdef CONFIG_METADATA_HUB -#include "metadata_hub.h" -#endif - -#ifdef CONFIG_MQTT -#include "mqtt.h" -#endif - -#ifdef AF_INET6 -#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN -#else -#define INETx_ADDRSTRLEN INET_ADDRSTRLEN -#endif - -#ifdef CONFIG_AIRPLAY_2 -#include "pair_ap/pair.h" -#include "plist/plist.h" -#include "plist_xml_strings.h" -#include "ptp-utilities.h" -#endif - -#ifdef CONFIG_DBUS_INTERFACE -#include "dbus-service.h" -#endif - -#include "mdns.h" - -// mDNS advertisement strings - -// Create these strings and then keep them updated. -// When necessary, update the mDNS service records, using e.g. Avahi -// from these sources. - -char *txt_records[64]; -char *secondary_txt_records[64]; - -char firmware_version[64]; -char ap1_featuresString[64]; -char pkString[128]; -#ifdef CONFIG_AIRPLAY_2 -char deviceIdString[64]; -char featuresString[64]; -char statusflagsString[32]; -char piString[128]; -char gidString[128]; -#endif - -#define METADATA_SNDBUF (4 * 1024 * 1024) - -enum rtsp_read_request_response { - rtsp_read_request_response_ok, - rtsp_read_request_response_immediate_shutdown_requested, - rtsp_read_request_response_bad_packet, - rtsp_read_request_response_channel_closed, - rtsp_read_request_response_read_error, - rtsp_read_request_response_error -}; - -rtsp_conn_info *playing_conn; -rtsp_conn_info **conns; - -int metadata_running = 0; - -// always lock this when accessing the playing conn value -pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER; - -// always lock this when accessing the list of connection threads -pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER; - -// every time we want to retain or release a reference count, lock it with this -// if a reference count is read as zero, it means the it's being deallocated. -static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; - -// only one thread is allowed to use the player at once. -// it monitors the request variable (at least when interrupted) -// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; -// static int please_shutdown = 0; -// static pthread_t playing_thread = 0; - -int RTSP_connection_index = 1; - -#ifdef CONFIG_METADATA -typedef struct { - pthread_mutex_t pc_queue_lock; - pthread_cond_t pc_queue_item_added_signal; - pthread_cond_t pc_queue_item_removed_signal; - char *name; - size_t item_size; // number of bytes in each item - uint32_t count; // number of items in the queue - uint32_t capacity; // maximum number of items - uint32_t toq; // first item to take - uint32_t eoq; // free space at end of queue - void *items; // a pointer to where the items are actually stored -} pc_queue; // producer-consumer queue -#endif - -static int msg_indexes = 1; - -typedef struct { - int index_number; - uint32_t referenceCount; // we might start using this... - unsigned int nheaders; - char *name[16]; - char *value[16]; - - uint32_t contentlength; - char *content; - - // for requests - char method[16]; - char path[256]; - - // for responses - int respcode; -} rtsp_message; - -#ifdef CONFIG_AIRPLAY_2 - -int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) { - int response = 0; - void *p = *allocation; - if (p == NULL) { - p = malloc(strlen(s) + 1); - if (p == NULL) { - debug(1, "error allocating memory"); - } else { - *allocation = p; - *size = *size + strlen(s) + 1; - uint8_t *b = (uint8_t *)p; - *b = strlen(s); - p = p + 1; - memcpy(p, s, strlen(s)); - response = 1; - } - } else { - p = realloc(p, *size + strlen(s) + 1); - if (p == NULL) { // assuming we never allocate a zero byte space - debug(1, "error reallocating memory"); - } else { - *allocation = p; - uint8_t *b = (uint8_t *)p + *size; - *b = strlen(s); - p = p + *size + 1; - memcpy(p, s, strlen(s)); - *size = *size + strlen(s) + 1; - response = 1; - } - } - return response; -} - -static void pkString_make(char *str, size_t str_size, const char *device_id) { - uint8_t public_key[32]; - if (str_size < 2 * sizeof(public_key) + 1) { - warn("Insufficient string size"); - str[0] = '\0'; - return; - } - pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, device_id); - char *ptr = str; - for (size_t i = 0; i < sizeof(public_key); i++) - ptr += sprintf(ptr, "%02x", public_key[i]); -} -#endif - -#ifdef CONFIG_AIRPLAY_2 -void build_bonjour_strings(rtsp_conn_info *conn) { -#else -void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) { -#endif - - int entry_number = 0; - - // make up a firmware version -#ifdef CONFIG_USE_GIT_VERSION_STRING - if (git_version_string[0] != '\0') - snprintf(firmware_version, sizeof(firmware_version), "fv=%s", git_version_string); - else -#endif - snprintf(firmware_version, sizeof(firmware_version), "fv=%s", PACKAGE_VERSION); - -#ifdef CONFIG_AIRPLAY_2 - uint64_t features_hi = config.airplay_features; - features_hi = (features_hi >> 32) & 0xffffffff; - uint64_t features_lo = config.airplay_features; - features_lo = features_lo & 0xffffffff; - snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "", - features_lo, features_hi); - snprintf(pkString, sizeof(pkString), "pk="); - pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="), - config.airplay_device_id); - - txt_records[entry_number++] = "cn=0,1"; - txt_records[entry_number++] = "da=true"; - txt_records[entry_number++] = "et=0,4"; - txt_records[entry_number++] = ap1_featuresString; - txt_records[entry_number++] = firmware_version; - txt_records[entry_number++] = "md=2"; - txt_records[entry_number++] = "am=Shairport Sync"; - txt_records[entry_number++] = "sf=0x4"; - txt_records[entry_number++] = "tp=UDP"; - txt_records[entry_number++] = "vn=65537"; - txt_records[entry_number++] = "vs=366.0"; - txt_records[entry_number++] = pkString; - txt_records[entry_number++] = NULL; - -#else - // here, just replicate what happens in mdns.h when using those #defines - txt_records[entry_number++] = "sf=0x4"; - txt_records[entry_number++] = firmware_version; - txt_records[entry_number++] = "am=ShairportSync"; - txt_records[entry_number++] = "vs=105.1"; - txt_records[entry_number++] = "tp=TCP,UDP"; - txt_records[entry_number++] = "vn=65537"; -#ifdef CONFIG_METADATA - if (config.get_coverart == 0) - txt_records[entry_number++] = "md=0,2"; - else - txt_records[entry_number++] = "md=0,1,2"; -#endif - txt_records[entry_number++] = "ss=16"; - txt_records[entry_number++] = "sr=44100"; - txt_records[entry_number++] = "da=true"; - txt_records[entry_number++] = "sv=false"; - txt_records[entry_number++] = "et=0,1"; - txt_records[entry_number++] = "ek=1"; - txt_records[entry_number++] = "cn=0,1"; - txt_records[entry_number++] = "ch=2"; - txt_records[entry_number++] = "txtvers=1"; - if (config.password == 0) - txt_records[entry_number++] = "pw=false"; - else - txt_records[entry_number++] = "pw=true"; - txt_records[entry_number++] = NULL; -#endif - -#ifdef CONFIG_AIRPLAY_2 - // make up a secondary set of text records - entry_number = 0; - - secondary_txt_records[entry_number++] = "srcvers=366.0"; - snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id); - secondary_txt_records[entry_number++] = deviceIdString; - snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "", - features_lo, features_hi); - secondary_txt_records[entry_number++] = featuresString; - snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32, - config.airplay_statusflags); - - secondary_txt_records[entry_number++] = statusflagsString; - secondary_txt_records[entry_number++] = "protovers=1.1"; - secondary_txt_records[entry_number++] = "acl=0"; - secondary_txt_records[entry_number++] = "rsf=0x0"; - secondary_txt_records[entry_number++] = firmware_version; - secondary_txt_records[entry_number++] = "model=Shairport Sync"; - snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi); - secondary_txt_records[entry_number++] = piString; - if ((conn != NULL) && (conn->airplay_gid != 0)) { - snprintf(gidString, sizeof(gidString), "gid=%s", conn->airplay_gid); - } else { - snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_pi); - } - secondary_txt_records[entry_number++] = gidString; - if ((conn != NULL) && (conn->groupContainsGroupLeader != 0)) - secondary_txt_records[entry_number++] = "gcgl=1"; - else - secondary_txt_records[entry_number++] = "gcgl=0"; - if ((conn != NULL) && (conn->airplay_gid != 0)) // if it's in a group - secondary_txt_records[entry_number++] = "isGroupLeader=0"; - secondary_txt_records[entry_number++] = pkString; - secondary_txt_records[entry_number++] = NULL; -#endif -} - -#ifdef CONFIG_METADATA -typedef struct { - uint32_t type; - uint32_t code; - char *data; - uint32_t length; - rtsp_message *carrier; -} metadata_package; - -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, - const char *name) { - if (name) - debug(2, "Creating metadata queue \"%s\".", name); - else - debug(1, "Creating an unnamed metadata queue."); - pthread_mutex_init(&the_queue->pc_queue_lock, NULL); - pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); - pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); - the_queue->item_size = item_size; - the_queue->items = items; - the_queue->count = 0; - the_queue->capacity = number_of_items; - the_queue->toq = 0; - the_queue->eoq = 0; - if (name == NULL) - the_queue->name = NULL; - else - the_queue->name = strdup(name); -} - -void pc_queue_delete(pc_queue *the_queue) { - if (the_queue->name) - debug(2, "Deleting metadata queue \"%s\".", the_queue->name); - else - debug(1, "Deleting an unnamed metadata queue."); - if (the_queue->name != NULL) - free(the_queue->name); - // debug(2, "destroying pc_queue_item_removed_signal"); - pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); - // debug(2, "destroying pc_queue_item_added_signal"); - pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); - // debug(2, "destroying pc_queue_lock"); - pthread_mutex_destroy(&the_queue->pc_queue_lock); - // debug(2, "destroying signals and locks done"); -} - -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, - int block); - -int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { - return send_metadata('ssnc', code, data, length, NULL, block); -} - -void pc_queue_cleanup_handler(void *arg) { - // debug(1, "pc_queue_cleanup_handler called."); - pc_queue *the_queue = (pc_queue *)arg; - int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item."); -} - -int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { - int response = 0; - int rc; - if (the_queue) { - if (block == 0) { - rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 2); - if (rc == EBUSY) - return EBUSY; - } else - rc = pthread_mutex_lock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error locking for pc_queue_add_item"); - pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); - // leave this out if you want this to return if the queue is already full - // irrespective of the block flag. - /* - while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, - &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); - } - */ - if (the_queue->count < the_queue->capacity) { - uint32_t i = the_queue->eoq; - void *p = the_queue->items + the_queue->item_size * i; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; - memcpy(p, the_stuff, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->eoq = i; - the_queue->count++; - // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, - // the_queue->capacity); - if (the_queue->count == the_queue->capacity) - debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, - the_queue->count); - rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); - if (rc) - debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", - the_queue->name); - } else { - response = EWOULDBLOCK; // a bit arbitrary, this. - debug(3, - "metadata queue \"%s\": is already full with %d items in it. Not adding this item to " - "the queue.", - the_queue->name, the_queue->count); - } - pthread_cleanup_pop(1); // unlock the queue lock. - } else { - debug(1, "Adding an item to a NULL queue"); - } - return response; -} - -int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { - int rc; - if (the_queue) { - rc = pthread_mutex_lock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name); - pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); - while (the_queue->count == 0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); - if (rc) - debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name); - } - uint32_t i = the_queue->toq; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; - void *p = the_queue->items + the_queue->item_size * i; - memcpy(the_stuff, p, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->toq = i; - the_queue->count--; - debug(3, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, - the_queue->capacity); - rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); - if (rc) - debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name); - pthread_cleanup_pop(1); // unlock the queue lock. - } else { - debug(1, "Removing an item from a NULL queue"); - } - return 0; -} - -#endif - -void lock_player() { debug_mutex_lock(&playing_conn_lock, 1000000, 3); } - -void unlock_player() { debug_mutex_unlock(&playing_conn_lock, 3); } - -int have_play_lock(rtsp_conn_info *conn) { - int response = 0; - lock_player(); - if (playing_conn == conn) // this connection definitely has the play lock - response = 1; - unlock_player(); - return response; -} - -/* -// return 0 if the playing_conn isn't already locked by someone else and if -// it belongs to the conn passed in. -// remember to release it! -int try_to_get_and_lock_playing_conn(rtsp_conn_info **conn) { - int response = -1; - if (pthread_mutex_trylock(&playing_conn_lock) == 0) { - response = 0; - *conn = playing_conn; - } - return response; -} - - -void release_hold_on_play_lock(__attribute__((unused)) rtsp_conn_info *conn) { - pthread_mutex_unlock(&playing_conn_lock); -} - -*/ - -// make conn no longer the playing_conn -void release_play_lock(rtsp_conn_info *conn) { - if (conn != NULL) - debug(2, "Connection %d: release play lock.", conn->connection_number); - else - debug(2, "Release play lock."); - lock_player(); - if (playing_conn == conn) { // if we have the player - if (conn != NULL) - debug(2, "Connection %d: play lock released.", conn->connection_number); - else - debug(2, "Play lock released."); - playing_conn = NULL; // let it go - } - unlock_player(); -} - -// make conn the playing_conn, and kill the current session if permitted -int get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) { - if (conn != NULL) - debug(2, "Connection %d: request play lock.", conn->connection_number); - else if (playing_conn != NULL) - debug(2, "Connection %d: request release.", playing_conn->connection_number); - else - debug(2, "Request release of non-existent player."); - // returns -1 if it failed, 0 if it succeeded and 1 if it succeeded but - // interrupted an existing session - int response = 0; - - int have_the_player = 0; - int should_wait = 0; // this will be true if you're trying to break in to the current session - int interrupting_current_session = 0; - - // try to become the current playing_conn - - lock_player(); // don't let it change while we are looking at it... - - if (playing_conn == NULL) { - playing_conn = conn; - have_the_player = 1; - } else if (playing_conn == conn) { - have_the_player = 1; - if (conn != NULL) - warn("Duplicate attempt to acquire the player by the same connection, by the look of it!"); - } else if (playing_conn->stop) { - debug(1, "Connection %d: Waiting for Connection %d to stop playing.", conn->connection_number, - playing_conn->connection_number); - should_wait = 1; - } else if (allow_session_interruption != 0) { - debug(2, "Asking Connection %d to stop playing.", playing_conn->connection_number); - playing_conn->stop = 1; - interrupting_current_session = 1; - should_wait = 1; - pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit - } - unlock_player(); - - if (should_wait) { - int time_remaining = 3000000; // must be signed, as it could go negative... - - while ((time_remaining > 0) && (have_the_player == 0)) { - lock_player(); // get it - if (playing_conn == NULL) { - playing_conn = conn; - have_the_player = 1; - } - unlock_player(); - - if (have_the_player == 0) { - usleep(100000); - time_remaining -= 100000; - } - } - if ((have_the_player == 1) && (interrupting_current_session == 1)) { - if (conn != NULL) - debug(2, "Connection %d: Got player lock", conn->connection_number); - else - debug(2, "Player released."); - response = 1; - } else { - debug(2, "Connection %d: failed to get player lock after waiting.", conn->connection_number); - response = -1; - } - } - - if ((have_the_player == 1) && (interrupting_current_session == 0)) { - if (conn != NULL) - debug(2, "Connection %d: Got player lock.", conn->connection_number); - else - debug(2, "Player released."); - response = 0; - } - return response; -} - -void player_watchdog_thread_cleanup_handler(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(3, "Connection %d: Watchdog Exit.", conn->connection_number); -} - -void *player_watchdog_thread_code(void *arg) { - pthread_cleanup_push(player_watchdog_thread_cleanup_handler, arg); - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - do { - usleep(2000000); // check every two seconds - // debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number); -#ifdef CONFIG_AIRPLAY_2 - if ((config.dont_check_timeout == 0) && (config.timeout != 0) && (conn->airplay_type == ap_1)) { -#else - if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { -#endif - debug_mutex_lock(&conn->watchdog_mutex, 1000, 0); - uint64_t last_watchdog_bark_time = conn->watchdog_bark_time; - debug_mutex_unlock(&conn->watchdog_mutex, 0); - if (last_watchdog_bark_time != 0) { - uint64_t time_since_last_bark = - (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000; - uint64_t ct = config.timeout; // go from int to 64-bit int - - if (time_since_last_bark >= ct) { - conn->watchdog_barks++; - if (conn->watchdog_barks == 1) { - // debuglev = 3; // tell us everything. - debug(1, - "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " - "of the heart\".", - conn->connection_number); - conn->stop = 1; - pthread_cancel(conn->thread); - } else if (conn->watchdog_barks == 3) { - if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { - conn->unfixable_error_reported = 1; - command_execute(config.cmd_unfixable, "unable_to_cancel_play_session", 1); - } else { - die("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", - conn->connection_number); - } - } - } - } - } - } while (1); - pthread_cleanup_pop(0); // should never happen - pthread_exit(NULL); -} - -// keep track of the threads we have spawned so we can join() them -static int nconns = 0; -static void track_thread(rtsp_conn_info *conn) { - debug_mutex_lock(&conns_lock, 1000000, 3); - // look for an empty slot first - int i = 0; - int found = 0; - while ((i < nconns) && (found == 0)) { - if (conns[i] == NULL) - found = 1; - else - i++; - } - if (found != 0) { - conns[i] = conn; - } else { - // make space for a new element - conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1)); - if (conns) { - conns[nconns] = conn; - nconns++; - } else { - die("could not reallocate memory for conns"); - } - } - debug_mutex_unlock(&conns_lock, 3); -} - -// note: connection numbers start at 1, so an except_this_one value of zero means "all threads" -void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_one) { - // if the stream category is unspecified_stream_category - // all categories are elegible for cancellation - // otherwise just the category itself - debug_mutex_lock(&conns_lock, 1000000, 3); - int i; - for (i = 0; i < nconns; i++) { - if ((conns[i] != NULL) && (conns[i]->running != 0) && - (conns[i]->connection_number != except_this_one) && - ((stream_category == unspecified_stream_category) || - (stream_category == conns[i]->airplay_stream_category))) { - pthread_cancel(conns[i]->thread); - debug(2, "Connection %d: cancelled.", conns[i]->connection_number); - } - } - for (i = 0; i < nconns; i++) { - if ((conns[i] != NULL) && (conns[i]->running != 0) && - (conns[i]->connection_number != except_this_one) && - ((stream_category == unspecified_stream_category) || - (stream_category == conns[i]->airplay_stream_category))) { - pthread_join(conns[i]->thread, NULL); - debug(2, "Connection %d: joined.", conns[i]->connection_number); - free(conns[i]); - conns[i] = NULL; - } - } - debug_mutex_unlock(&conns_lock, 3); -} - -void cleanup_threads(void) { - - void *retval; - int i; - // debug(2, "culling threads."); - debug_mutex_lock(&conns_lock, 1000000, 3); - for (i = 0; i < nconns; i++) { - if ((conns[i] != NULL) && (conns[i]->running == 0)) { - debug(3, "found RTSP connection thread %d in a non-running state.", - conns[i]->connection_number); - pthread_join(conns[i]->thread, &retval); - debug(2, "Connection %d: deleted in cleanup.", conns[i]->connection_number); - free(conns[i]); - conns[i] = NULL; - } - } - debug_mutex_unlock(&conns_lock, 3); -} - -// park a null at the line ending, and return the next line pointer -// accept \r, \n, or \r\n -static char *nextline(char *in, int inbuf) { - char *out = NULL; - while (inbuf) { - if (*in == '\r') { - *in++ = 0; - out = in; - inbuf--; - } - if ((*in == '\n') && (inbuf)) { - *in++ = 0; - out = in; - } - - if (out) - break; - - in++; - inbuf--; - } - return out; -} - -void msg_retain(rtsp_message *msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock"); - if (msg > (rtsp_message *)0x00010000) { - msg->referenceCount++; - debug(3, "msg_free increment reference counter message %d to %d.", msg->index_number, - msg->referenceCount); - // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock"); - } else { - debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t)msg); - } -} - -rtsp_message *msg_init(void) { - rtsp_message *msg = malloc(sizeof(rtsp_message)); - if (msg) { - memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = 1; // from now on, any access to this must be protected with the lock - msg->index_number = msg_indexes++; - debug(3, "msg_init message %d", msg->index_number); - } else { - die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); - } - // debug(1,"msg_init -- create item %d.", msg->index_number); - return msg; -} - -int msg_add_header(rtsp_message *msg, char *name, char *value) { - if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { - warn("too many headers?!"); - return 1; - } - - msg->name[msg->nheaders] = strdup(name); - msg->value[msg->nheaders] = strdup(value); - msg->nheaders++; - - return 0; -} - -char *msg_get_header(rtsp_message *msg, char *name) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) - if (!strcasecmp(msg->name[i], name)) - return msg->value[i]; - return NULL; -} - -void _debug_print_msg_headers(const char *filename, const int linenumber, int level, - rtsp_message *msg) { - unsigned int i; - if (msg->respcode != 0) - _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode); - for (i = 0; i < msg->nheaders; i++) { - _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i], - msg->value[i]); - } -} - -/* -static void debug_print_msg_content(int level, rtsp_message *msg) { - if (msg->contentlength) { - char *obf = malloc(msg->contentlength * 2 + 1); - if (obf) { - char *obfp = obf; - int obfc; - for (obfc = 0; obfc < msg->contentlength; obfc++) { - snprintf(obfp, 3, "%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"); - } - } else { - debug(level, "No content"); - } -} -*/ - -void msg_free(rtsp_message **msgh) { - debug_mutex_lock(&reference_counter_lock, 1000, 0); - if (*msgh > (rtsp_message *)0x00010000) { - rtsp_message *msg = *msgh; - msg->referenceCount--; - if (msg->referenceCount) - debug(3, "msg_free decrement reference counter message %d to %d", msg->index_number, - msg->referenceCount); - if (msg->referenceCount == 0) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); - } - if (msg->content) - free(msg->content); - // debug(1,"msg_free item %d -- free.",msg->index_number); - uintptr_t index = (msg->index_number) & 0xFFFF; - if (index == 0) - index = 0x10000; // ensure it doesn't fold to zero. - *msgh = - (rtsp_message *)(index); // put a version of the index number of the freed message in here - debug(3, "msg_free freed message %d", msg->index_number); - free(msg); - } else { - // debug(1,"msg_free item %d -- decrement reference to - // %d.",msg->index_number,msg->referenceCount); - } - } else if (*msgh != NULL) { - debug(1, - "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " - "%d.", - (uintptr_t)*msgh); - } - debug_mutex_unlock(&reference_counter_lock, 0); -} - -int msg_handle_line(rtsp_message **pmsg, char *line) { - rtsp_message *msg = *pmsg; - - if (!msg) { - msg = msg_init(); - *pmsg = msg; - char *sp, *p; - sp = NULL; // this is to quieten a compiler warning - - debug(3, "RTSP Message Received: \"%s\".", line); - - p = strtok_r(line, " ", &sp); - if (!p) - goto fail; - strncpy(msg->method, p, sizeof(msg->method) - 1); - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - strncpy(msg->path, p, sizeof(msg->path) - 1); - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - if ((strcmp(p, "RTSP/1.0")) && (strcmp(p, "HTTP/1.1"))) { - debug(1,"can't find a RTSP or HTTP header in the following."); - - char obf[4096]; - char *obfp = obf; - int obfc; - for (obfc=0;obfc<20;obfc++) { - snprintf(obfp, 3, "%02X", line[obfc]); - obfp+=2; - }; - *obfp=0; - debug(1,"Funny Packet Received: \"%s\"",obf); - goto fail; - } - - return -1; - } - - if (strlen(line)) { - char *p; - p = strstr(line, ": "); - if (!p) { - warn("bad header: >>%s<<", line); - goto fail; - } - *p = 0; - p += 2; - msg_add_header(msg, line, p); - debug(3, " %s: %s.", line, p); - return -1; - } else { - char *cl = msg_get_header(msg, "Content-Length"); - if (cl) - return atoi(cl); - else - return 0; - } - -fail: - debug(3, "msg_handle_line fail"); - msg_free(pmsg); - *pmsg = NULL; - return 0; -} - -#ifdef CONFIG_AIRPLAY_2 - -void add_flush_request(int flushNow, uint32_t flushFromSeq, uint32_t flushFromTS, - uint32_t flushUntilSeq, uint32_t flushUntilTS, rtsp_conn_info *conn) { - // immediate flush requests are added sequentially. Don't know how more than one could arise, TBH - flush_request_t **t = &conn->flush_requests; - int done = 0; - do { - flush_request_t *u = *t; - if ((u == NULL) || ((u->flushNow == 0) && (flushNow != 0)) || - (flushFromSeq < u->flushFromSeq) || - ((flushFromSeq == u->flushFromSeq) && (flushFromTS < u->flushFromTS))) { - flush_request_t *n = (flush_request_t *)calloc(sizeof(flush_request_t), 1); - n->flushNow = flushNow; - n->flushFromSeq = flushFromSeq; - n->flushFromTS = flushFromTS; - n->flushUntilSeq = flushUntilSeq; - n->flushUntilTS = flushUntilTS; - n->next = u; - *t = n; - done = 1; - } else { - t = &u->next; - } - } while (done == 0); -} - -void display_all_flush_requests(rtsp_conn_info *conn) { - if (conn->flush_requests == NULL) { - debug(1, "No flush requests."); - } else { - flush_request_t *t = conn->flush_requests; - do { - if (t->flushNow) { - debug(1, "immediate flush to untilSeq: %u, untilTS: %u.", t->flushUntilSeq, - t->flushUntilTS); - } else { - debug(1, "fromSeq: %u, fromTS: %u, to untilSeq: %u, untilTS: %u.", t->flushFromSeq, - t->flushFromTS, t->flushUntilSeq, t->flushUntilTS); - } - t = t->next; - } while (t != NULL); - } -} - -int rtsp_message_contains_plist(rtsp_message *message) { - int reply = 0; // assume there is no plist in the message - if ((message->contentlength >= strlen("bplist00")) && - (strstr(message->content, "bplist00") == message->content)) - reply = 1; - return reply; -} - -plist_t plist_from_rtsp_content(rtsp_message *message) { - plist_t the_plist = NULL; - if (rtsp_message_contains_plist(message)) { - plist_from_memory(message->content, message->contentlength, &the_plist); - } - return the_plist; -} - -char *plist_content(plist_t the_plist) { - // caller must free the returned character buffer - // convert it to xml format - uint32_t size; - char *plist_out = NULL; - plist_to_xml(the_plist, &plist_out, &size); - - // put it into a NUL-terminated string - char *reply = malloc(size + 1); - if (reply) { - memcpy(reply, plist_out, size); - reply[size] = '\0'; - } - if (the_plist) - plist_free(the_plist); - if (plist_out) - free(plist_out); - return reply; -} - -// caller must free the returned character buffer -char *rtsp_plist_content(rtsp_message *message) { - char *reply = NULL; - // first, check if it has binary plist content - if (rtsp_message_contains_plist(message)) { - // get the plist from the content - - plist_t the_plist = plist_from_rtsp_content(message); - - // convert it to xml format - uint32_t size; - char *plist_out = NULL; - plist_to_xml(the_plist, &plist_out, &size); - - // put it into a NUL-terminated string - reply = malloc(size + 1); - if (reply) { - memcpy(reply, plist_out, size); - reply[size] = '\0'; - } - if (the_plist) - plist_free(the_plist); - if (plist_out) - free(plist_out); - } - return reply; -} - -#endif - -void _debug_log_rtsp_message(const char *filename, const int linenumber, int level, char *prompt, - rtsp_message *message) { - if (level > debuglev) - return; - if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list... - _debug(filename, linenumber, level, prompt); - _debug_print_msg_headers(filename, linenumber, level, message); -#ifdef CONFIG_AIRPLAY_2 - char *plist_content = rtsp_plist_content(message); - if (plist_content) { - _debug(filename, linenumber, level, " Content Plist (as XML):\n--\n%s--", plist_content); - free(plist_content); - } else -#endif - { - char *content = message->content; - char obf[4096]; - char *obfp = obf; - uint32_t obfc; - for (obfc=0; obfccontentlength; obfc++) { - snprintf(obfp, 3, "%02X", content[obfc]); - obfp+=2; - }; - *obfp=0; - _debug(filename, linenumber, level, " Cannot find a plist. Content length: %d, content \"%s\".", - message->contentlength, obf); - - } -} - -#define debug_log_rtsp_message(level, prompt, message) \ - _debug_log_rtsp_message(__FILE__, __LINE__, level, prompt, message) -#define debug_print_msg_headers(level, message) \ - _debug_print_msg_headers(__FILE__, __LINE__, level, message) - -#ifdef CONFIG_AIRPLAY_2 -static void buf_add(sized_buffer *buf, uint8_t *in, size_t in_len) { - if (buf->length + in_len > buf->size) { - buf->size = buf->length + in_len + 2048; // Extra legroom to avoid future memcpy's - uint8_t *new = malloc(buf->size); - memcpy(new, buf->data, buf->length); - free(buf->data); - buf->data = new; - } - memcpy(buf->data + buf->length, in, in_len); - buf->length += in_len; -} - -static void buf_drain(sized_buffer *buf, ssize_t len) { - if (len < 0 || (size_t)len >= buf->length) { - free(buf->data); - memset(buf, 0, sizeof(sized_buffer)); - return; - } - memmove(buf->data, buf->data + len, buf->length - len); - buf->length -= len; -} - -static size_t buf_remove(sized_buffer *buf, uint8_t *out, size_t out_len) { - size_t bytes = (buf->length > out_len) ? out_len : buf->length; - memcpy(out, buf->data, bytes); - buf_drain(buf, bytes); - return bytes; -} - -static ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count) { - uint8_t in[4096]; - uint8_t *plain; - size_t plain_len; - - // If there is leftover decoded content from the last pass just return that - if (ctx->plaintext_read_buffer.length > 0) { - return buf_remove(&ctx->plaintext_read_buffer, buf, count); - } - - do { - ssize_t got = read(fd, in, sizeof(in)); - if (got <= 0) - return got; - buf_add(&ctx->encrypted_read_buffer, in, got); - - ssize_t consumed = pair_decrypt(&plain, &plain_len, ctx->encrypted_read_buffer.data, - ctx->encrypted_read_buffer.length, ctx->cipher_ctx); - if (consumed < 0) - return -1; - buf_drain(&ctx->encrypted_read_buffer, consumed); - } while (plain_len == 0); - - // Fast path, avoids some memcpy + allocs in case of the normal, small message - /* if (ctx->plaintext_read_buffer.len == 0 && plain_len < count) { - memcpy(buf, plain, plain_len); - free(plain); - return plain_len; - } - */ - buf_add(&ctx->plaintext_read_buffer, plain, plain_len); - free(plain); - - return buf_remove(&ctx->plaintext_read_buffer, buf, count); -} - -static ssize_t write_encrypted(int fd, pair_cipher_bundle *ctx, const void *buf, size_t count) { - uint8_t *encrypted; - size_t encrypted_len; - - ssize_t ret = pair_encrypt(&encrypted, &encrypted_len, buf, count, ctx->cipher_ctx); - if (ret < 0) { - debug(1, pair_cipher_errmsg(ctx->cipher_ctx)); - return -1; - } - - size_t remain = encrypted_len; - while (remain > 0) { - ssize_t wrote = write(fd, encrypted + (encrypted_len - remain), remain); - if (wrote <= 0) { - free(encrypted); - return wrote; - } - remain -= wrote; - } - free(encrypted); - return count; -} - -/* -static ssize_t write_encrypted(rtsp_conn_info *conn, const void *buf, size_t count) { - uint8_t *encrypted; - size_t encrypted_len; - - ssize_t ret = - pair_encrypt(&encrypted, &encrypted_len, buf, count, conn->ap2_pairing_context.cipher_ctx); - if (ret < 0) { - debug(1, pair_cipher_errmsg(conn->ap2_pairing_context.cipher_ctx)); - return -1; - } - - size_t remain = encrypted_len; - while (remain > 0) { - ssize_t wrote = write(conn->fd, encrypted + (encrypted_len - remain), remain); - if (wrote <= 0) { - free(encrypted); - return wrote; - } - remain -= wrote; - } - free(encrypted); - return count; -} -*/ -#endif - -ssize_t timed_read_from_rtsp_connection(rtsp_conn_info *conn, uint64_t wait_time, void *buf, - size_t count) { - // note: a wait time of zero means wait forever - ssize_t result = 0; // closed - if (conn->fd > 0) { - int64_t remaining_time = 0; - uint64_t time_to_wait_to = get_absolute_time_in_ns();; - time_to_wait_to = time_to_wait_to + wait_time; - - // remaining_time will be zero if wait_time is zero - if (wait_time != 0) { - remaining_time = time_to_wait_to - get_absolute_time_in_ns(); - } - do { - struct timeval tv; - tv.tv_sec = remaining_time / 1000000000; // seconds - tv.tv_usec = (remaining_time % 1000000000) / 1000; // microseconds - if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) != 0) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "could not set time limit on timed_read_from_rtsp_connection -- error %d \"%s\".", - errno, errorstring); - } - -#ifdef CONFIG_AIRPLAY_2 - if (conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { - conn->ap2_pairing_context.control_cipher_bundle.is_encrypted = 1; - result = read_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, buf, count); - } else { - result = read(conn->fd, buf, count); - } -#else - result = read(conn->fd, buf, count); -#endif - if (wait_time != 0) - remaining_time = time_to_wait_to - get_absolute_time_in_ns(); - if (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) && (remaining_time > 0)) - debug(1,"remaining time on a timed read is %" PRId64 " ns.", remaining_time); - } while (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) && (remaining_time > 0)); - - } else { - debug(1,"Connection %d: attempt to read from a closed RTSP connection.",conn->connection_number); - } - return result; -} - -#ifdef CONFIG_AIRPLAY_2 -void set_client_as_ptp_clock(rtsp_conn_info *conn) { - char timing_list_message[4096] = ""; - strncat(timing_list_message, "T ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - strncat(timing_list_message, (const char *)&conn->client_ip_string, - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - ptp_send_control_message_string(timing_list_message); -} - -void clear_ptp_clock() { - ptp_send_control_message_string("T"); -} -#endif - -ssize_t read_from_rtsp_connection(rtsp_conn_info *conn, void *buf, size_t count) { - // first try to read with a timeout, to see if there is any traffic... - // ssize_t response = timed_read_from_rtsp_connection(conn, 20000000000L, buf, count); - // actually don't use a timeout -- OwnTone doesn't supply regular traffic. - ssize_t response = timed_read_from_rtsp_connection(conn, 0, buf, count); - if ((response == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) { - if (conn->rtsp_link_is_idle == 0) { - debug(1, "Connection %d: RTSP connection is idle.", conn->connection_number); - conn->rtsp_link_is_idle = 1; - conn->udp_clock_sender_is_initialised = 0; - conn->udp_clock_is_initialised = 0; - } - response = timed_read_from_rtsp_connection(conn, 0, buf, count); - } - if (conn->rtsp_link_is_idle == 1) { - conn->rtsp_link_is_idle = 0; - debug(1, "Connection %d: RTSP connection traffic has resumed.", conn->connection_number); -#ifdef CONFIG_AIRPLAY_2 - if (conn->airplay_stream_type == realtime_stream) { - conn->last_anchor_info_is_valid = 0; - conn->anchor_remote_info_is_valid = 0; - conn->first_packet_timestamp = 0; - conn->input_frame_rate_starting_point_is_valid = 0; - ab_resync(conn); - } -#else - conn->anchor_remote_info_is_valid = 0; - conn->local_to_remote_time_difference_measurement_time = 0; - conn->local_to_remote_time_difference = 0; - conn->first_packet_timestamp = 0; - conn->input_frame_rate_starting_point_is_valid = 0; - ab_resync(conn); -#endif - } - return response; -} - -enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { - - *the_packet = NULL; // need this for error handling - - enum rtsp_read_request_response reply = rtsp_read_request_response_ok; - ssize_t buflen = 4096; -#ifdef CONFIG_METADATA - if ((config.metadata_enabled != 0) && (config.get_coverart != 0)) - buflen = 1024 * 256; // big enough for typical picture data, which will be base64 encoded -#endif - int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay - char *buf = malloc(buflen + 1); // add a NUL at the end - if (!buf) { - warn("Connection %d: rtsp_read_request: can't get a buffer.", conn->connection_number); - return (rtsp_read_request_response_error); - } - pthread_cleanup_push(malloc_cleanup, buf); - ssize_t nread; - ssize_t inbuf = 0; - int msg_size = -1; - - while (msg_size < 0) { - if (conn->stop != 0) { - debug(3, "Connection %d: shutdown requested.", conn->connection_number); - reply = rtsp_read_request_response_immediate_shutdown_requested; - goto shutdown; - } - - nread = read_from_rtsp_connection(conn, buf + inbuf, buflen - inbuf); - - if (nread == 0) { - // a blocking read that returns zero means eof -- implies connection closed by client - debug(1, "Connection %d: RTSP connection closed by client.", - conn->connection_number); - reply = rtsp_read_request_response_channel_closed; - // Note: the socket will be closed when the thread exits - goto shutdown; - } - - if (nread < 0) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) { - debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", - conn->connection_number); - continue; - } - if (errno != ECONNRESET) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - if (errno != 0) - debug(2, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", - conn->connection_number, errno, (char *)errorstring); - } - reply = rtsp_read_request_response_read_error; - goto shutdown; - } -/* - // this outputs the message received - { - void *pt = malloc(nread+1); - memset(pt, 0, nread+1); - memcpy(pt, buf + inbuf, nread); - debug(1, "Incoming string on RTSP port: \"%s\"",pt); - free(pt); - } - */ - - inbuf += nread; - - char *next; - while (msg_size < 0 && (next = nextline(buf, inbuf))) { - msg_size = msg_handle_line(the_packet, buf); - - if (!(*the_packet)) { - debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.", - conn->connection_number); - reply = rtsp_read_request_response_bad_packet; - goto shutdown; - } - - inbuf -= next - buf; - if (inbuf) - memmove(buf, next, inbuf); - } - } - - if (msg_size > buflen) { - buf = realloc(buf, msg_size + 1); - if (!buf) { - warn("Connection %d: too much content.", conn->connection_number); - reply = rtsp_read_request_response_error; - goto shutdown; - } - buflen = msg_size; - } - - uint64_t threshold_time = - get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now - int warning_message_sent = 0; - - // const size_t max_read_chunk = 1024 * 1024 / 16; - while (inbuf < msg_size) { - - // we are going to read the stream in chunks and time how long it takes to - // do so. - // If it's taking too long, (and we find out about it), we will send an - // error message as - // metadata - - if (warning_message_sent == 0) { - uint64_t time_now = get_absolute_time_in_ns(); - if (time_now > threshold_time) { // it's taking too long - debug(1, "Error receiving metadata from source -- transmission seems " - "to be stalled."); -#ifdef CONFIG_METADATA - send_ssnc_metadata('stal', NULL, 0, 1); -#endif - warning_message_sent = 1; - } - } - - if (conn->stop != 0) { - debug(1, "RTSP shutdown requested."); - reply = rtsp_read_request_response_immediate_shutdown_requested; - goto shutdown; - } - size_t read_chunk = msg_size - inbuf; - // if (read_chunk > max_read_chunk) - // read_chunk = max_read_chunk; - // usleep(80000); // wait about 80 milliseconds between reads of up to max_read_chunk - nread = read_from_rtsp_connection(conn, buf + inbuf, read_chunk); - if (!nread) { - reply = rtsp_read_request_response_error; - goto shutdown; - } - if (nread < 0) { - if (errno == EINTR) - continue; - if (errno == EAGAIN) { - debug(1, "Getting Error 11 -- EAGAIN from a blocking read!"); - continue; - } - if (errno != ECONNRESET) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", - conn->connection_number, errno, (char *)errorstring); - } - reply = rtsp_read_request_response_read_error; - goto shutdown; - } - inbuf += nread; - } - - rtsp_message *msg = *the_packet; - msg->contentlength = inbuf; - msg->content = buf; - char *jp = inbuf + buf; - *jp = '\0'; - *the_packet = msg; -shutdown: - if (reply != rtsp_read_request_response_ok) { - msg_free(the_packet); - release_buffer = 1; // allow the buffer to be released - } - pthread_cleanup_pop(release_buffer); - return reply; -} - -int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) { - char pkt[4096]; - int pktfree = sizeof(pkt); - char *p = pkt; - int n; - unsigned int i; - - struct response_t { - int code; - char *string; - }; - - struct response_t responses[] = {{200, "OK"}, - {400, "Bad Request"}, - {403, "Unauthorized"}, - {404, "Not Found"}, - {451, "Unavailable"}, - {456, "Header Field Not Valid for Resource"}, - {470, "Connection Authorization Required"}, - {500, "Internal Server Error"}, - {501, "Not Implemented"}}; - // 451 is really "Unavailable For Legal Reasons"! - int found = 0; - char *respcode_text = "Unauthorized"; - for (i = 0; i < sizeof(responses) / sizeof(struct response_t); i++) { - if (resp->respcode == responses[i].code) { - found = 1; - respcode_text = responses[i].string; - } - } - - if (found == 0) - debug(1, "can't find text for response code %d. Using \"%s\" instead.", resp->respcode, - respcode_text); - - n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, respcode_text); - pktfree -= n; - p += n; - - for (i = 0; i < resp->nheaders; i++) { - // debug(3, " %s: %s.", resp->name[i], resp->value[i]); - n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); - pktfree -= n; - p += n; - if (pktfree <= 1024) { - debug(1, "Attempted to write overlong RTSP packet 1"); - return -1; - } - } - - // Here, if there's content, write the Content-Length header ... - - if (resp->contentlength) { - debug(2, "Responding with content of length %d", resp->contentlength); - n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength); - pktfree -= n; - p += n; - if (pktfree <= 1024) { - debug(1, "Attempted to write overlong RTSP packet 2"); - return -2; - } - } - - n = snprintf(p, pktfree, "\r\n"); - pktfree -= n; - p += n; - - if (resp->contentlength) { - memcpy(p, resp->content, resp->contentlength); - pktfree -= resp->contentlength; - p += resp->contentlength; - } - - if (pktfree <= 1024) { - debug(1, "Attempted to write overlong RTSP packet 3"); - return -3; - } - - // here, if the link is encrypted, better do it - -#ifdef CONFIG_AIRPLAY_2 - ssize_t reply; - if (conn->ap2_pairing_context.control_cipher_bundle.is_encrypted) { - reply = - write_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, pkt, p - pkt); - } else { - reply = write(conn->fd, pkt, p - pkt); - } -#else - ssize_t reply = write(conn->fd, pkt, p - pkt); -#endif - - if (reply == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "msg_write_response error %d: \"%s\".", errno, (char *)errorstring); - return -4; - } - if (reply != p - pkt) { - debug(1, "msg_write_response error -- requested bytes: %d not fully written: %d.", p - pkt, - reply); - return -5; - } - return 0; -} - -char *get_category_string(airplay_stream_c cat) { - char *category; - switch (cat) { - case unspecified_stream_category: - category = "unspecified stream"; - break; - case ptp_stream: - category = "PTP stream"; - break; - case ntp_stream: - category = "NTP stream"; - break; - case remote_control_stream: - category = "Remote Control stream"; - break; - default: - category = "Unexpected stream code"; - break; - } - return category; -} - -void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, - rtsp_message *resp) { - debug(2, "Connection %d: RECORD on %s", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - // debug_log_rtsp_message(1, "RECORD incoming message", req); - resp->respcode = 200; -} - -void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: RECORD", conn->connection_number); - if (have_play_lock(conn)) { - if (conn->player_thread) - warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number); - else { - debug(1, "Connection %d: Classic AirPlay connection from %s:%u to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - conn->self_ip_string, conn->self_rtsp_port); - activity_monitor_signify_activity(1); - player_prepare_to_play(conn); - player_play(conn); // the thread better be 0 - } - - resp->respcode = 200; - // I think this is for telling the client what the absolute minimum latency - // actually is, - // and when the client specifies a latency, it should be added to this figure. - - // Thus, [the old version of] AirPlay's latency figure of 77175, when added to 11025 gives you - // exactly 88200 - // and iTunes' latency figure of 88553, when added to 11025 gives you 99578, - // pretty close to the 99400 we guessed. - - msg_add_header(resp, "Audio-Latency", "11025"); - - char *p; - uint32_t rtptime = 0; - char *hdr = msg_get_header(req, "RTP-Info"); - - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '='); - if (p) { - rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 - // rtptime--; - // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); - player_flush(rtptime, conn); - } - } - } - } else { - warn("Connection %d RECORD received without having the player (no ANNOUNCE?)", - conn->connection_number); - resp->respcode = 451; - } -} - -#ifdef CONFIG_AIRPLAY_2 - - -void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { - debug_log_rtsp_message(2, "GET /info:", req); - if (rtsp_message_contains_plist(req)) { // it's stage one - // get version of AirPlay -- it might be too old. Not using it yet. - char *hdr = msg_get_header(req, "User-Agent"); - if (hdr) { - if (strstr(hdr, "AirPlay/") == hdr) { - hdr = hdr + strlen("AirPlay/"); - // double airplay_version = 0.0; - // airplay_version = atof(hdr); - debug(2, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number, - hdr); - } - } - plist_t info_plist = NULL; - plist_from_memory(req->content, req->contentlength, &info_plist); - - plist_t qualifier = plist_dict_get_item(info_plist, "qualifier"); - if (qualifier == NULL) { - debug(1, "GET /info Stage 1: plist->qualifier was NULL"); - goto user_fail; - } - if (plist_array_get_size(qualifier) < 1) { - debug(1, "GET /info Stage 1: plist->qualifier array length < 1"); - goto user_fail; - } - plist_t qualifier_array_value = plist_array_get_item(qualifier, 0); - char *qualifier_array_val_cstr; - plist_get_string_val(qualifier_array_value, &qualifier_array_val_cstr); - if (qualifier_array_val_cstr == NULL) { - debug(1, "GET /info Stage 1: first item in qualifier array not a string"); - goto user_fail; - } - debug(2, "GET /info Stage 1: qualifier: %s", qualifier_array_val_cstr); - plist_free(info_plist); - free(qualifier_array_val_cstr); - - // uint8_t bt_addr[6] = {0xB8, 0x27, 0xEB, 0xB7, 0xD4, 0x0E}; - plist_t response_plist = NULL; - plist_from_xml((const char *)plists_get_info_response_xml, plists_get_info_response_xml_len, - &response_plist); - if (response_plist == NULL) { - debug(1, "GET /info Stage 1: response plist not created from XML!"); - } else { - void *qualifier_response_data = NULL; - size_t qualifier_response_data_length = 0; - if (add_pstring_to_malloc("acl=0", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc(deviceIdString, &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc(featuresString, &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("rsf=0x0", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("flags=0x4", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("model=Shairport Sync", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("manufacturer=", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("serialNumber=", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("protovers=1.1", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("srcvers=366.0", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc(piString, &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc(gidString, &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - if (add_pstring_to_malloc("gcgl=0", &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - snprintf(pkString, sizeof(pkString), "pk="); - pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="), - config.airplay_device_id); - if (add_pstring_to_malloc(pkString, &qualifier_response_data, - &qualifier_response_data_length) == 0) - debug(1, "Problem"); - // debug(1,"qualifier_response_data_length: %u.", qualifier_response_data_length); - - plist_dict_set_item(response_plist, "txtAirPlay", - plist_new_data(qualifier_response_data, qualifier_response_data_length)); - - plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features)); - plist_dict_set_item(response_plist, "statusFlags", - plist_new_uint(config.airplay_statusflags)); - plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id)); - plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi)); - plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name)); - char *vs = get_version_string(); - // plist_dict_set_item(response_plist, "model", plist_new_string(vs)); - plist_dict_set_item(response_plist, "model", plist_new_string("Shairport Sync")); - free(vs); - // pkString_make(pkString, sizeof(pkString), config.airplay_device_id); - // plist_dict_set_item(response_plist, "pk", plist_new_string(pkString)); - plist_to_bin(response_plist, &resp->content, &resp->contentlength); - if (resp->contentlength == 0) - debug(1, "GET /info Stage 1: response bplist not created!"); - plist_free(response_plist); - free(qualifier_response_data); - } - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "GET /info Stage 1 Response:", resp); - resp->respcode = 200; - return; - - user_fail: - resp->respcode = 400; - return; - } else { // stage two - plist_t response_plist = NULL; - plist_from_xml((const char *)plists_get_info_response_xml, plists_get_info_response_xml_len, - &response_plist); - plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features)); - plist_dict_set_item(response_plist, "statusFlags", plist_new_uint(config.airplay_statusflags)); - plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id)); - plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi)); - plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name)); - char *vs = get_version_string(); - // plist_dict_set_item(response_plist, "model", plist_new_string(vs)); - plist_dict_set_item(response_plist, "model", plist_new_string("Shairport Sync")); - free(vs); - // pkString_make(pkString, sizeof(pkString), config.airplay_device_id); - // plist_dict_set_item(response_plist, "pk", plist_new_string(pkString)); - plist_to_bin(response_plist, &resp->content, &resp->contentlength); - plist_free(response_plist); - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "GET /info Stage 2 Response", resp); - resp->respcode = 200; - return; - } -} - -void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: FLUSHBUFFERED %s : Content-Length %d", conn->connection_number, - req->path, req->contentlength); - debug_log_rtsp_message(2, "FLUSHBUFFERED request", req); - - uint64_t flushFromSeq = 0; - uint64_t flushFromTS = 0; - uint64_t flushUntilSeq = 0; - uint64_t flushUntilTS = 0; - int flushFromValid = 0; - plist_t messagePlist = plist_from_rtsp_content(req); - if (messagePlist != NULL) { - plist_t item = plist_dict_get_item(messagePlist, "flushFromSeq"); - if (item == NULL) { - debug(2, "Can't find a flushFromSeq"); - } else { - flushFromValid = 1; - plist_get_uint_val(item, &flushFromSeq); - debug(2, "flushFromSeq is %" PRId64 ".", flushFromSeq); - } - - item = plist_dict_get_item(messagePlist, "flushFromTS"); - if (item == NULL) { - if (flushFromValid != 0) - debug(1, "flushFromSeq without flushFromTS!"); - else - debug(2, "Can't find a flushFromTS"); - } else { - plist_get_uint_val(item, &flushFromTS); - if (flushFromValid == 0) - debug(1, "flushFromTS without flushFromSeq!"); - debug(2, "flushFromTS is %" PRId64 ".", flushFromTS); - } - - item = plist_dict_get_item(messagePlist, "flushUntilSeq"); - if (item == NULL) { - debug(1, "Can't find the flushUntilSeq"); - } else { - plist_get_uint_val(item, &flushUntilSeq); - debug(2, "flushUntilSeq is %" PRId64 ".", flushUntilSeq); - } - - item = plist_dict_get_item(messagePlist, "flushUntilTS"); - if (item == NULL) { - debug(1, "Can't find the flushUntilTS"); - } else { - plist_get_uint_val(item, &flushUntilTS); - debug(2, "flushUntilTS is %" PRId64 ".", flushUntilTS); - } - - debug_mutex_lock(&conn->flush_mutex, 1000, 1); - // a flush with from... components will not be followed by a setanchor (i.e. a play) - // if it's a flush that will be followed by a setanchor (i.e. a play) then stop play now. - if (flushFromValid == 0) - conn->ap2_play_enabled = 0; - - // add the exact request as made to the linked list (not used for anything but diagnostics now) - // int flushNow = 0; - // if (flushFromValid == 0) - // flushNow = 1; - // add_flush_request(flushNow, flushFromSeq, flushFromTS, flushUntilSeq, flushUntilTS, conn); - - // now, if it's an immediate flush, replace the existing request, if any - // but it if's a deferred flush and there is an existing deferred request, - // only update the flushUntil stuff -- that seems to preserve - // the intended semantics - - // so, always replace these - conn->ap2_flush_until_sequence_number = flushUntilSeq; - conn->ap2_flush_until_rtp_timestamp = flushUntilTS; - - if ((conn->ap2_flush_requested != 0) && (conn->ap2_flush_from_valid != 0) && - (flushFromValid != 0)) { - // if there is a request already, and it's a deferred request, and the current request is also - // deferred... do nothing! -- leave the starting point in place. Yeah, yeah, we know de Morgan's - // Law, but this seems clearer - } else { - conn->ap2_flush_from_sequence_number = flushFromSeq; - conn->ap2_flush_from_rtp_timestamp = flushFromTS; - } - - conn->ap2_flush_from_valid = flushFromValid; - conn->ap2_flush_requested = 1; - - // reflect the possibly updated flush request - // add_flush_request(flushNow, conn->ap2_flush_from_sequence_number, - // conn->ap2_flush_from_rtp_timestamp, conn->ap2_flush_until_sequence_number, - // conn->ap2_flush_until_rtp_timestamp, conn); - - debug_mutex_unlock(&conn->flush_mutex, 3); - - if (flushFromValid) - debug(2, "Deferred Flush Requested"); - else - debug(2, "Immediate Flush Requested"); - - plist_free(messagePlist); - // display_all_flush_requests(conn); - } - - resp->respcode = 200; -} - -void handle_setrate(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: SETRATE %s : Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, "SETRATE request -- unimplemented", req); - resp->respcode = 501; // Not Implemented -} - -void handle_unimplemented_ap1(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { - debug_log_rtsp_message(1, "request not recognised for AirPlay 1 operation", req); - resp->respcode = 501; -} - -void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: SETRATEANCHORI %s :: Content-Length %d", conn->connection_number, - req->path, req->contentlength); - - plist_t messagePlist = plist_from_rtsp_content(req); - - if (messagePlist != NULL) { - pthread_cleanup_push(plist_cleanup, (void *)messagePlist); - plist_t item = plist_dict_get_item(messagePlist, "networkTimeSecs"); - if (item != NULL) { - plist_t item_2 = plist_dict_get_item(messagePlist, "networkTimeTimelineID"); - if (item_2 == NULL) { - debug(1, "Can't identify the Clock ID of the player."); - } else { - uint64_t nid; - plist_get_uint_val(item_2, &nid); - debug(2, "networkTimeTimelineID \"%" PRIx64 "\".", nid); - conn->networkTimeTimelineID = nid; - } - uint64_t networkTimeSecs; - plist_get_uint_val(item, &networkTimeSecs); - debug(2, "anchor networkTimeSecs is %" PRIu64 ".", networkTimeSecs); - - item = plist_dict_get_item(messagePlist, "networkTimeFrac"); - uint64_t networkTimeFrac; - plist_get_uint_val(item, &networkTimeFrac); - debug(2, "anchor networkTimeFrac is 0%" PRIu64 ".", networkTimeFrac); - // it looks like the networkTimeFrac is a fraction where the msb is work 1/2, the - // next 1/4 and so on - // now, convert the network time and fraction into nanoseconds - networkTimeFrac = networkTimeFrac >> 32; // reduce precision to about 1/4 nanosecond - networkTimeFrac = networkTimeFrac * 1000000000; - networkTimeFrac = networkTimeFrac >> 32; // we should now be left with the ns - - networkTimeSecs = networkTimeSecs * 1000000000; // turn the whole seconds into ns - uint64_t anchorTimeNanoseconds = networkTimeSecs + networkTimeFrac; - - debug(2, "anchorTimeNanoseconds looks like %" PRIu64 ".", anchorTimeNanoseconds); - - item = plist_dict_get_item(messagePlist, "rtpTime"); - uint64_t rtpTime; - - plist_get_uint_val(item, &rtpTime); - // debug(1, "anchor rtpTime is %" PRId64 ".", rtpTime); - uint32_t anchorRTPTime = rtpTime; - - int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate); - // debug(1,"anchorRTPTime: %" PRIu32 ", added latency: %" PRId32 ".", anchorRTPTime, - // added_latency); - set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime - added_latency, - anchorTimeNanoseconds); - } - - item = plist_dict_get_item(messagePlist, "rate"); - if (item != NULL) { - uint64_t rate; - plist_get_uint_val(item, &rate); - debug(3, "anchor rate 0x%016" PRIx64 ".", rate); - debug_mutex_lock(&conn->flush_mutex, 1000, 1); - pthread_cleanup_push(mutex_unlock, &conn->flush_mutex); - conn->ap2_rate = rate; - if ((rate & 1) != 0) { - debug(2, "Connection %d: Start playing, with anchor clock %" PRIx64 ".", - conn->connection_number, conn->networkTimeTimelineID); - activity_monitor_signify_activity(1); - -#ifdef CONFIG_METADATA - send_ssnc_metadata('pres', NULL, 0, 1); // resume -- contains cancellation points -#endif - conn->ap2_play_enabled = 1; - } else { - debug(2, "Connection %d: Stop playing.", conn->connection_number); - conn->ap2_play_enabled = 0; - activity_monitor_signify_activity(0); - reset_anchor_info(conn); -#ifdef CONFIG_METADATA - send_ssnc_metadata('paus', NULL, 0, 1); // pause -- contains cancellation points -#endif - if (config.output->stop) { - debug(2, "Connection %d: Stop the output backend.", conn->connection_number); - config.output->stop(); - } - } - pthread_cleanup_pop(1); // unlock the conn->flush_mutex - } - pthread_cleanup_pop(1); // plist_free the messagePlist; - } else { - debug(1, "missing plist!"); - } - resp->respcode = 200; -} - -void handle_get(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: GET %s :: Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, "GET request", req); - if (strcmp(req->path, "/info") == 0) { - handle_get_info(conn, req, resp); - } else { - debug(1, "Unhandled GET, path \"%s\".", req->path); - resp->respcode = 501; // Not Implemented - } -} - -#else -void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, - __attribute((unused)) rtsp_message *resp) { - debug(1, "Connection %d: GET %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - resp->respcode = 500; -} - -void handle_post(rtsp_conn_info *conn, rtsp_message *req, - __attribute((unused)) rtsp_message *resp) { - debug(1, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - resp->respcode = 500; -} -#endif - -#ifdef CONFIG_AIRPLAY_2 -struct pairings { - char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; - uint8_t public_key[32]; - - struct pairings *next; -} * pairings; - -static struct pairings *pairing_find(const char *device_id) { - for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { - if (strcmp(device_id, pairing->device_id) == 0) - return pairing; - } - return NULL; -} - -static void pairing_add(uint8_t public_key[32], const char *device_id) { - struct pairings *pairing = calloc(1, sizeof(struct pairings)); - snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id); - memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); - - pairing->next = pairings; - pairings = pairing; -} - -static void pairing_remove(struct pairings *pairing) { - if (pairing == pairings) { - pairings = pairing->next; - } else { - struct pairings *iter; - for (iter = pairings; iter && (iter->next != pairing); iter = iter->next) - ; /* EMPTY */ - - if (iter) - iter->next = pairing->next; - } - - free(pairing); -} - -static int pairing_add_cb(uint8_t public_key[32], const char *device_id, - void *cb_arg __attribute__((unused))) { - debug(1, "pair-add cb for %s", device_id); - - struct pairings *pairing = pairing_find(device_id); - if (pairing) { - memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); - return 0; - } - - pairing_add(public_key, device_id); - return 0; -} - -static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, - void *cb_arg __attribute__((unused))) { - debug(1, "pair-remove cb for %s", device_id); - - struct pairings *pairing = pairing_find(device_id); - if (!pairing) { - debug(1, "pair-remove callback for unknown device"); - return -1; - } - - pairing_remove(pairing); - return 0; -} - -static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, - void *cb_arg __attribute__((unused))) { - debug(2, "pair-list cb"); - - for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { - enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg); - } -} - -void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, - rtsp_message *resp) { - uint8_t *body = NULL; - size_t body_len = 0; - int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, - (const uint8_t *)req->content, req->contentlength); - if (ret < 0) { - debug(1, "pair-add returned an error"); - resp->respcode = 451; - return; - } - resp->content = (char *)body; // these will be freed when the data is sent - resp->contentlength = body_len; - msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-add response", resp); -} - -void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, - rtsp_message *resp) { - uint8_t *body = NULL; - size_t body_len = 0; - int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, - (const uint8_t *)req->content, req->contentlength); - if (ret < 0) { - debug(1, "pair-list returned an error"); - resp->respcode = 451; - return; - } - resp->content = (char *)body; // these will be freed when the data is sent - resp->contentlength = body_len; - msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-list response", resp); -} - -void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, - rtsp_message *resp) { - uint8_t *body = NULL; - size_t body_len = 0; - int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, - (const uint8_t *)req->content, req->contentlength); - if (ret < 0) { - debug(1, "pair-remove returned an error"); - resp->respcode = 451; - return; - } - resp->content = (char *)body; // these will be freed when the data is sent - resp->contentlength = body_len; - msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-remove response", resp); -} - -void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - int ret; - uint8_t *body = NULL; - size_t body_len = 0; - struct pair_result *result; - debug(2, "Connection %d: pair-verify Content-Length %d", conn->connection_number, - req->contentlength); - - if (!conn->ap2_pairing_context.verify_ctx) { - conn->ap2_pairing_context.verify_ctx = - pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, NULL, NULL, config.airplay_device_id); - if (!conn->ap2_pairing_context.verify_ctx) { - debug(1, "Error creating verify context"); - resp->respcode = 500; // Internal Server Error - goto out; - } - } - - ret = pair_verify(&body, &body_len, conn->ap2_pairing_context.verify_ctx, - (const uint8_t *)req->content, req->contentlength); - if (ret < 0) { - debug(1, pair_verify_errmsg(conn->ap2_pairing_context.verify_ctx)); - resp->respcode = 470; // Connection Authorization Required - goto out; - } - - ret = pair_verify_result(&result, conn->ap2_pairing_context.verify_ctx); - if (ret == 0 && result->shared_secret_len > 0) { - conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx = - pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, result->shared_secret, result->shared_secret_len); - if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { - debug(1, "Error setting up rtsp control channel ciphering\n"); - goto out; - } - } - -out: - resp->content = (char *)body; // these will be freed when the data is sent - resp->contentlength = body_len; - if (body) - msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-verify response", resp); -} - -void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug_log_rtsp_message(1, "pair-setup", req); - int ret; - uint8_t *body = NULL; - size_t body_len = 0; - struct pair_result *result; - debug(2, "Connection %d: handle_pair-setup Content-Length %d", conn->connection_number, - req->contentlength); - - if (!conn->ap2_pairing_context.setup_ctx) { - conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.airplay_pin, - NULL, NULL, config.airplay_device_id); - if (!conn->ap2_pairing_context.setup_ctx) { - debug(1, "Error creating setup context"); - resp->respcode = 500; // Internal Server Error - goto out; - } - } - - ret = pair_setup(&body, &body_len, conn->ap2_pairing_context.setup_ctx, - (const uint8_t *)req->content, req->contentlength); - if (ret < 0) { - debug(1, pair_setup_errmsg(conn->ap2_pairing_context.setup_ctx)); - resp->respcode = 470; // Connection Authorization Required - goto out; - } - - ret = pair_setup_result(NULL, &result, conn->ap2_pairing_context.setup_ctx); - if (ret == 0 && result->shared_secret_len > 0) { - // Transient pairing completed (pair-setup step 2), prepare encryption, but - // don't activate yet, the response to this request is still plaintext - conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx = - pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, result->shared_secret, result->shared_secret_len); - if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { - debug(1, "Error setting up rtsp control channel ciphering\n"); - goto out; - } - } - -out: - resp->content = (char *)body; // these will be freed when the data is sent - resp->contentlength = body_len; - if (body) - msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(1, "pair-setup response", resp); -} - -void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { - - /* Fairplay magic */ - static uint8_t server_fp_reply1[] = - "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a" - "\x25\x21\xdb\xdf\x31\x2a\xb2\xbf\xb2\x9e\x8d\x23\x2b\x63\x76\xa8\xc8\x18\x70" - "\x1d\x22\xae\x93\xd8\x27\x37\xfe\xaf\x9d\xb4\xfd\xf4\x1c\x2d\xba\x9d\x1f\x49" - "\xca\xaa\xbf\x65\x91\xac\x1f\x7b\xc6\xf7\xe0\x66\x3d\x21\xaf\xe0\x15\x65\x95" - "\x3e\xab\x81\xf4\x18\xce\xed\x09\x5a\xdb\x7c\x3d\x0e\x25\x49\x09\xa7\x98\x31" - "\xd4\x9c\x39\x82\x97\x34\x34\xfa\xcb\x42\xc6\x3a\x1c\xd9\x11\xa6\xfe\x94\x1a" - "\x8a\x6d\x4a\x74\x3b\x46\xc3\xa7\x64\x9e\x44\xc7\x89\x55\xe4\x9d\x81\x55\x00" - "\x95\x49\xc4\xe2\xf7\xa3\xf6\xd5\xba"; - static uint8_t server_fp_reply2[] = - "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x01\xcf\x32\xa2\x57\x14" - "\xb2\x52\x4f\x8a\xa0\xad\x7a\xf1\x64\xe3\x7b\xcf\x44\x24\xe2\x00\x04\x7e\xfc" - "\x0a\xd6\x7a\xfc\xd9\x5d\xed\x1c\x27\x30\xbb\x59\x1b\x96\x2e\xd6\x3a\x9c\x4d" - "\xed\x88\xba\x8f\xc7\x8d\xe6\x4d\x91\xcc\xfd\x5c\x7b\x56\xda\x88\xe3\x1f\x5c" - "\xce\xaf\xc7\x43\x19\x95\xa0\x16\x65\xa5\x4e\x19\x39\xd2\x5b\x94\xdb\x64\xb9" - "\xe4\x5d\x8d\x06\x3e\x1e\x6a\xf0\x7e\x96\x56\x16\x2b\x0e\xfa\x40\x42\x75\xea" - "\x5a\x44\xd9\x59\x1c\x72\x56\xb9\xfb\xe6\x51\x38\x98\xb8\x02\x27\x72\x19\x88" - "\x57\x16\x50\x94\x2a\xd9\x46\x68\x8a"; - static uint8_t server_fp_reply3[] = - "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x02\xc1\x69\xa3\x52\xee" - "\xed\x35\xb1\x8c\xdd\x9c\x58\xd6\x4f\x16\xc1\x51\x9a\x89\xeb\x53\x17\xbd\x0d" - "\x43\x36\xcd\x68\xf6\x38\xff\x9d\x01\x6a\x5b\x52\xb7\xfa\x92\x16\xb2\xb6\x54" - "\x82\xc7\x84\x44\x11\x81\x21\xa2\xc7\xfe\xd8\x3d\xb7\x11\x9e\x91\x82\xaa\xd7" - "\xd1\x8c\x70\x63\xe2\xa4\x57\x55\x59\x10\xaf\x9e\x0e\xfc\x76\x34\x7d\x16\x40" - "\x43\x80\x7f\x58\x1e\xe4\xfb\xe4\x2c\xa9\xde\xdc\x1b\x5e\xb2\xa3\xaa\x3d\x2e" - "\xcd\x59\xe7\xee\xe7\x0b\x36\x29\xf2\x2a\xfd\x16\x1d\x87\x73\x53\xdd\xb9\x9a" - "\xdc\x8e\x07\x00\x6e\x56\xf8\x50\xce"; - static uint8_t server_fp_reply4[] = - "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x03\x90\x01\xe1\x72\x7e" - "\x0f\x57\xf9\xf5\x88\x0d\xb1\x04\xa6\x25\x7a\x23\xf5\xcf\xff\x1a\xbb\xe1\xe9" - "\x30\x45\x25\x1a\xfb\x97\xeb\x9f\xc0\x01\x1e\xbe\x0f\x3a\x81\xdf\x5b\x69\x1d" - "\x76\xac\xb2\xf7\xa5\xc7\x08\xe3\xd3\x28\xf5\x6b\xb3\x9d\xbd\xe5\xf2\x9c\x8a" - "\x17\xf4\x81\x48\x7e\x3a\xe8\x63\xc6\x78\x32\x54\x22\xe6\xf7\x8e\x16\x6d\x18" - "\xaa\x7f\xd6\x36\x25\x8b\xce\x28\x72\x6f\x66\x1f\x73\x88\x93\xce\x44\x31\x1e" - "\x4b\xe6\xc0\x53\x51\x93\xe5\xef\x72\xe8\x68\x62\x33\x72\x9c\x22\x7d\x82\x0c" - "\x99\x94\x45\xd8\x92\x46\xc8\xc3\x59"; - - static uint8_t server_fp_header[] = "\x46\x50\x4c\x59\x03\x01\x04\x00\x00\x00\x00\x14"; - - resp->respcode = 200; // assume it's handled - - // uint8_t *out; - // size_t out_len; - int version_pos = 4; - int mode_pos = 14; - int type_pos = 5; - int seq_pos = 6; - int setup_message_type = 1; - int setup1_message_seq = 1; - int setup2_message_seq = 3; - int setup2_suffix_len = 20; - // int ret; - - // response and len are dummy values and can be ignored - - // debug(1, "Version: %02x, mode: %02x, type: %02x, seq: %02x", req->content[version_pos], - // req->content[mode_pos], req->content[type_pos], req->content[seq_pos]); - - if (req->content[version_pos] != 3 || req->content[type_pos] != setup_message_type) { - debug(1, "Unsupported FP version."); - } - - char *response = NULL; - size_t len = 0; - - if (req->content[seq_pos] == setup1_message_seq) { - // All replies are the same length. -1 to account for the NUL byte at the end. - len = sizeof(server_fp_reply1) - 1; - - if (req->content[mode_pos] == 0) - response = memdup(server_fp_reply1, len); - if (req->content[mode_pos] == 1) - response = memdup(server_fp_reply2, len); - if (req->content[mode_pos] == 2) - response = memdup(server_fp_reply3, len); - if (req->content[mode_pos] == 3) - response = memdup(server_fp_reply4, len); - - } else if (req->content[seq_pos] == setup2_message_seq) { - // -1 to account for the NUL byte at the end. - len = sizeof(server_fp_header) - 1 + setup2_suffix_len; - response = malloc(len); - if (response) { - memcpy(response, server_fp_header, sizeof(server_fp_header) - 1); - memcpy(response + sizeof(server_fp_header) - 1, - req->content + req->contentlength - setup2_suffix_len, setup2_suffix_len); - } - } - - if (response == NULL) { - debug(1, "Cannot create a response."); - } - - resp->content = response; // these will be freed when the data is sent - resp->contentlength = len; - msg_add_header(resp, "Content-Type", "application/octet-stream"); -} - -/* - Identifier - 21cc689d-d5de-4814-872c-71d1426b57e0 - Enable_HK_Access_Control - - PublicKey - - qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ= - - Device_Name - MyDevice - Access_Control_Level - 0 -*/ -void handle_configure(rtsp_conn_info *conn __attribute__((unused)), - rtsp_message *req __attribute__((unused)), rtsp_message *resp) { - uint8_t public_key[32]; - - pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id); - - plist_t response_plist = plist_new_dict(); - - plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); - plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); - plist_dict_set_item(response_plist, "PublicKey", - plist_new_data((const char *)public_key, sizeof(public_key))); - plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); - plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); - - plist_to_bin(response_plist, &resp->content, &resp->contentlength); - plist_free(response_plist); - - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "POST /configure response:", resp); -} - -void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, NULL, req); - if (conn->airplay_stream_category == remote_control_stream) { - plist_t array_plist = plist_new_array(); - - plist_t response_plist = plist_new_dict(); - plist_dict_set_item(response_plist, "streams", array_plist); - - plist_to_bin(response_plist, &resp->content, &resp->contentlength); - plist_free(response_plist); - - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "FEEDBACK response (remote_control_stream):", resp); - } - - /* not finished yet - plist_t payload_plist = plist_new_dict(); - plist_dict_set_item(payload_plist, "type", plist_new_uint(103)); - plist_dict_set_item(payload_plist, "sr", plist_new_real(44100.0)); - - plist_t array_plist = plist_new_array(); - plist_array_append_item(array_plist, payload_plist); - - plist_t response_plist = plist_new_dict(); - plist_dict_set_item(response_plist, "streams",array_plist); - - plist_to_bin(response_plist, &resp->content, &resp->contentlength); - plist_free(response_plist); - // plist_free(array_plist); - // plist_free(payload_plist); - - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "FEEDBACK response:", resp); - */ -} - -void handle_command(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, NULL, req); - if (rtsp_message_contains_plist(req)) { - plist_t command_dict = NULL; - plist_from_memory(req->content, req->contentlength, &command_dict); - if (command_dict != NULL) { - // we have a plist -- try to get the dict item keyed to "updateMRSupportedCommands" - plist_t item = plist_dict_get_item(command_dict, "type"); - if (item != NULL) { - char *typeValue = NULL; - plist_get_string_val(item, &typeValue); - if ((typeValue != NULL) && (strcmp(typeValue, "updateMRSupportedCommands") == 0)) { - item = plist_dict_get_item(command_dict, "params"); - if (item != NULL) { - // the item should be a dict - plist_t item_array = plist_dict_get_item(item, "mrSupportedCommandsFromSender"); - if (item_array != NULL) { - // here we have an array of data items - uint32_t items = plist_array_get_size(item_array); - if (items) { - uint32_t item_number; - for (item_number = 0; item_number < items; item_number++) { - plist_t the_item = plist_array_get_item(item_array, item_number); - char *buff = NULL; - uint64_t length = 0; - plist_get_data_val(the_item, &buff, &length); - // debug(1,"Item %d, length: %" PRId64 " bytes", item_number, length); - if ((buff != NULL) && (length >= strlen("bplist00")) && - (strstr(buff, "bplist00") == buff)) { - // debug(1,"Contains a plist."); - plist_t subsidiary_plist = NULL; - plist_from_memory(buff, length, &subsidiary_plist); - if (subsidiary_plist) { - char *printable_plist = plist_content(subsidiary_plist); - if (printable_plist) { - debug(3, "\n%s", printable_plist); - free(printable_plist); - } else { - debug(1, "Can't print the plist!"); - } - // plist_free(subsidiary_plist); - } else { - debug(1, "Can't access the plist!"); - } - } - if (buff != NULL) - free(buff); - } - } - } else { - debug(1, "POST /command no mrSupportedCommandsFromSender item."); - } - } else { - debug(1, "POST /command no params dict."); - } - resp->respcode = 400; // say it's a bad request - } else { - debug(1, - "POST /command plist type is \"%s\", but \"updateMRSupportedCommands\" expected.", - typeValue); - } - if (typeValue != NULL) - free(typeValue); - } else { - debug(2, "Could not find a \"type\" item."); - } - - plist_free(command_dict); - } else { - debug(1, "POST /command plist cannot be inputted."); - } - } else { - debug(1, "POST /command contains no plist"); - } -} - -void handle_audio_mode(rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, NULL, req); -} - -void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - resp->respcode = 200; - if (strcmp(req->path, "/pair-setup") == 0) { - handle_pair_setup(conn, req, resp); - } else if (strcmp(req->path, "/pair-verify") == 0) { - handle_pair_verify(conn, req, resp); - } else if (strcmp(req->path, "/pair-add") == 0) { - handle_pair_add(conn, req, resp); - } else if (strcmp(req->path, "/pair-remove") == 0) { - handle_pair_remove(conn, req, resp); - } else if (strcmp(req->path, "/pair-list") == 0) { - handle_pair_list(conn, req, resp); - } else if (strcmp(req->path, "/fp-setup") == 0) { - handle_fp_setup(conn, req, resp); - } else if (strcmp(req->path, "/configure") == 0) { - handle_configure(conn, req, resp); - } else if (strcmp(req->path, "/feedback") == 0) { - handle_feedback(conn, req, resp); - } else if (strcmp(req->path, "/command") == 0) { - handle_command(conn, req, resp); - } else if (strcmp(req->path, "/audioMode") == 0) { - handle_audio_mode(conn, req, resp); - } else { - debug(1, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number, - req->path, req->contentlength); - debug_log_rtsp_message(2, "POST request", req); - } -} - -void handle_setpeers(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: SETPEERS %s Content-Length %d", conn->connection_number, req->path, - req->contentlength); - debug_log_rtsp_message(2, "SETPEERS request", req); -/* - char timing_list_message[4096]; - timing_list_message[0] = 'T'; - timing_list_message[1] = 0; - - // ensure the client itself is first -- it's okay if it's duplicated later - strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - strncat(timing_list_message, (const char *)&conn->client_ip_string, - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - - plist_t addresses_array = NULL; - plist_from_memory(req->content, req->contentlength, &addresses_array); - uint32_t items = plist_array_get_size(addresses_array); - if (items) { - uint32_t item; - for (item = 0; item < items; item++) { - plist_t n = plist_array_get_item(addresses_array, item); - char *ip_address = NULL; - plist_get_string_val(n, &ip_address); - // debug(1,ip_address); - strncat(timing_list_message, " ", - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - strncat(timing_list_message, ip_address, - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - if (ip_address != NULL) - free(ip_address); - } - ptp_send_control_message_string(timing_list_message); - } - plist_free(addresses_array); -*/ - //set_client_as_ptp_clock(conn); - resp->respcode = 200; -} -#endif - -#ifndef CONFIG_AIRPLAY_2 -void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { - debug_log_rtsp_message(2, "OPTIONS request", req); - debug(3, "Connection %d: OPTIONS", conn->connection_number); - resp->respcode = 200; - msg_add_header(resp, "Public", - "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); -} -#endif - -#ifdef CONFIG_AIRPLAY_2 - -void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { - debug_log_rtsp_message(2, "OPTIONS request", req); - debug(3, "Connection %d: OPTIONS", conn->connection_number); - resp->respcode = 200; - msg_add_header(resp, "Public", - "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, FLUSHBUFFERED, TEARDOWN, " - "OPTIONS, POST, GET, PUT"); -} - -void teardown_phase_one(rtsp_conn_info *conn) { - // this can be called more than once on the same connection -- - // by the player itself but also by the play session being killed - if (conn->player_thread) { - player_stop(conn); // this nulls the player_thread - activity_monitor_signify_activity(0); // inactive, and should be after command_stop() - } - if (conn->session_key) { - free(conn->session_key); - conn->session_key = NULL; - } -} - -void teardown_phase_two(rtsp_conn_info *conn) { - // we are being asked to disconnect - // this can be called more than once on the same connection -- - // by the player itself but also by the play seesion being killed - debug(2, "Connection %d: TEARDOWN %s connection.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - if (conn->airplay_stream_category == remote_control_stream) { - if (conn->rtp_data_thread) { - debug(2, "Connection %d: TEARDOWN %s Delete Data Thread.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - pthread_cancel(*conn->rtp_data_thread); - pthread_join(*conn->rtp_data_thread, NULL); - free(conn->rtp_data_thread); - conn->rtp_data_thread = NULL; - } - if (conn->data_socket) { - debug(2, "Connection %d: TEARDOWN %s Close Data Socket.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - close(conn->data_socket); - conn->data_socket = 0; - } - } - - if (conn->rtp_event_thread) { - debug(2, "Connection %d: TEARDOWN %s Delete Event Thread.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - pthread_cancel(*conn->rtp_event_thread); - pthread_join(*conn->rtp_event_thread, NULL); - free(conn->rtp_event_thread); - conn->rtp_event_thread = NULL; - } - if (conn->event_socket) { - debug(2, "Connection %d: TEARDOWN %s Close Event Socket.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - close(conn->event_socket); - conn->event_socket = 0; - } - - // if we are closing a PTP stream only, do this - if (conn->airplay_stream_category == ptp_stream) { - if (conn->airplay_gid != NULL) { - free(conn->airplay_gid); - conn->airplay_gid = NULL; - -#ifdef CONFIG_METADATA - // this is here to ensure it's only performed once during a teardown of a ptp stream - send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1); -#endif - } - conn->groupContainsGroupLeader = 0; - config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay - build_bonjour_strings(conn); - debug(2, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - mdns_update(NULL, secondary_txt_records); - if (conn->dacp_active_remote != NULL) { - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } - release_play_lock(conn); - clear_ptp_clock(); - } -} - -void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { - - debug_log_rtsp_message(2, "TEARDOWN: ", req); - // if (have_player(conn)) { - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); - - plist_t messagePlist = plist_from_rtsp_content(req); - if (messagePlist != NULL) { - // now see if the incoming plist contains a "streams" array - plist_t streams = plist_dict_get_item(messagePlist, "streams"); - - if (streams) { - debug(2, "Connection %d: TEARDOWN %s Close the stream.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - // we are being asked to close a stream - teardown_phase_one(conn); - plist_free(streams); - debug(2, "Connection %d: TEARDOWN %s Close the stream complete", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - } else { - debug(2, "Connection %d: TEARDOWN %s Close the connection.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - teardown_phase_one(conn); // try to do phase one anyway - teardown_phase_two(conn); - debug(2, "Connection %d: TEARDOWN %s Close the connection complete", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - } - //} else { - // warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)", - // conn->connection_number); - // resp->respcode = 451; - - plist_free(messagePlist); - resp->respcode = 200; - } else { - debug(1, "Connection %d: missing plist!", conn->connection_number); - resp->respcode = 451; // don't know what to do here - } - // debug(1,"Bogus exit for valgrind -- remember to comment it out!."); - // exit(EXIT_SUCCESS); // -} -#endif - -void teardown(rtsp_conn_info *conn) { - player_stop(conn); - activity_monitor_signify_activity(0); // inactive, and should be after command_stop() - if (conn->dacp_active_remote != NULL) { - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } -} - -void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, - rtsp_message *resp) { - debug_log_rtsp_message(2, "TEARDOWN request", req); - debug(2, "Connection %d: TEARDOWN", conn->connection_number); - if (have_play_lock(conn)) { - debug( - 3, - "TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).", - conn->connection_number); - teardown(conn); - release_play_lock(conn); - resp->respcode = 200; - msg_add_header(resp, "Connection", "close"); - debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.", - conn->connection_number); - } else { - warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)", - conn->connection_number); - resp->respcode = 451; - } - // debug(1,"Bogus exit for valgrind -- remember to comment it out!."); - // exit(EXIT_SUCCESS); -} - -void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug_log_rtsp_message(2, "FLUSH request", req); - debug(3, "Connection %d: FLUSH", conn->connection_number); - char *p = NULL; - uint32_t rtptime = 0; - char *hdr = msg_get_header(req, "RTP-Info"); - - if (hdr) { - // debug(1,"FLUSH message received: \"%s\".",hdr); - // get the rtp timestamp - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '='); - if (p) - rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 - } - } - debug(2, "RTSP Flush Requested: %u.", rtptime); - if (have_play_lock(conn)) { -#ifdef CONFIG_METADATA - if (p) - send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); - else - send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); -#endif - - player_flush(rtptime, conn); // will not crash even it there is no player thread. - resp->respcode = 200; - - } else { - warn("Connection %d FLUSH %u received without having the player", conn->connection_number, - rtptime); - resp->respcode = 451; - } -} - -#ifdef CONFIG_AIRPLAY_2 - -#ifdef CONFIG_METADATA -static void check_and_send_plist_metadata(plist_t messagePlist, const char *plist_key, - uint32_t metadata_code) { - plist_t item = plist_dict_get_item(messagePlist, plist_key); - if (item) { - char *value; - plist_get_string_val(item, &value); - if (value != NULL) { - send_metadata('ssnc', metadata_code, value, strlen(value), NULL, 0); - free(value); - } - } -} -#endif - -void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - int err; - debug(1, "Connection %d: SETUP (AirPlay 2)", conn->connection_number); - debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req); - - plist_t messagePlist = plist_from_rtsp_content(req); - plist_t setupResponsePlist = plist_new_dict(); - resp->respcode = 400; - - // see if we can get a name for the client - char *clientNameString = NULL; - plist_t nameItem = plist_dict_get_item(messagePlist, "name"); - if (nameItem != NULL) { - plist_get_string_val(nameItem, &clientNameString); - } else { - clientNameString = strdup(""); - } - - // see if the incoming plist contains a "streams" array - plist_t streams = plist_dict_get_item(messagePlist, "streams"); - if (streams == NULL) { - // no "streams" plist, so it must (?) be an initial setup - debug(2, - "Connection %d SETUP: No \"streams\" array has been found -- create an event thread " - "and open a TCP port.", - conn->connection_number); - conn->airplay_stream_category = unspecified_stream_category; - // figure out what category of stream it is, by looking at the plist - plist_t timingProtocol = plist_dict_get_item(messagePlist, "timingProtocol"); - if (timingProtocol != NULL) { - char *timingProtocolString = NULL; - plist_get_string_val(timingProtocol, &timingProtocolString); - if (timingProtocolString) { - if (strcmp(timingProtocolString, "PTP") == 0) { - debug(1, "Connection %d: AP2 PTP connection from %s:%u (\"%s\") to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, clientNameString, - conn->self_ip_string, conn->self_rtsp_port); - conn->airplay_stream_category = ptp_stream; - conn->timing_type = ts_ptp; -#ifdef CONFIG_METADATA - send_ssnc_metadata('conn', conn->client_ip_string, strlen(conn->client_ip_string), - 1); // before disconnecting an existing play -#endif - get_play_lock(conn, 1); // airplay 2 always allows interruption -#ifdef CONFIG_METADATA - send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1); - send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1); -#endif - } else if (strcmp(timingProtocolString, "NTP") == 0) { - debug(1, "Connection %d: SETUP: NTP setup from %s:%u (\"%s\") to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, clientNameString, - conn->self_ip_string, conn->self_rtsp_port); - conn->airplay_stream_category = ntp_stream; - conn->timing_type = ts_ntp; - } else if (strcmp(timingProtocolString, "None") == 0) { - debug(3, "Connection %d: SETUP: a \"None\" setup detected from %s:%u (\"%s\") to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, clientNameString, - conn->self_ip_string, conn->self_rtsp_port); - // now check to see if it's got the "isRemoteControlOnly" item and check it's true - plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly"); - if (isRemoteControlOnly != NULL) { - uint8_t isRemoteControlOnlyBoolean = 0; - plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean); - if (isRemoteControlOnlyBoolean != 0) { - debug(1, "Connection %d: Remote Control connection from %s:%u (\"%s\") to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, clientNameString, - conn->self_ip_string, conn->self_rtsp_port); - conn->airplay_stream_category = remote_control_stream; - } else { - debug(1, - "Connection %d: SETUP: a \"None\" setup detected, with " - "\"isRemoteControlOnly\" item set to \"false\".", - conn->connection_number); - } - } else { - debug(1, - "Connection %d: SETUP: a \"None\" setup detected, but no " - "\"isRemoteControlOnly\" item detected.", - conn->connection_number); - } - } - - // here, we know it's an initial setup and we know the kind of setup being requested - // if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and - // timingPeerList - if (conn->airplay_stream_category == ptp_stream) { - if (ptp_shm_interface_open() != - 0) // it should be open already, but just in case it isn't... - die("Can not access the NQPTP service. Has it stopped running?"); - // clear_ptp_clock(); - debug_log_rtsp_message(2, "SETUP \"PTP\" message", req); - plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID"); - if (groupUUID) { - char *gid = NULL; - plist_get_string_val(groupUUID, &gid); - if (gid) { - if (conn->airplay_gid) - free(conn->airplay_gid); - conn->airplay_gid = gid; // it'll be free'd later on... - } else { - debug(1, "Invalid groupUUID"); - } - } else { - debug(1, "No groupUUID in SETUP"); - } - - // now see if the group contains a group leader - plist_t groupContainsGroupLeader = - plist_dict_get_item(messagePlist, "groupContainsGroupLeader"); - if (groupContainsGroupLeader) { - uint8_t value = 0; - plist_get_bool_val(groupContainsGroupLeader, &value); - conn->groupContainsGroupLeader = value; - debug(2, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader); - } else { - debug(1, "No groupContainsGroupLeader in SETUP"); - } - - char timing_list_message[4096]; - timing_list_message[0] = 'T'; - timing_list_message[1] = 0; - - // ensure the client itself is first -- it's okay if it's duplicated later - strncat(timing_list_message, " ", - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - strncat(timing_list_message, (const char *)&conn->client_ip_string, - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - - plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo"); - if (timing_peer_info) { - // first, get the incoming plist. - plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses"); - if (addresses_array) { - // iterate through the array of items - uint32_t items = plist_array_get_size(addresses_array); - if (items) { - uint32_t item; - for (item = 0; item < items; item++) { - plist_t n = plist_array_get_item(addresses_array, item); - char *ip_address = NULL; - plist_get_string_val(n, &ip_address); - // debug(1, "Timing peer: %s", ip_address); - strncat(timing_list_message, " ", - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - strncat(timing_list_message, ip_address, - sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - free(ip_address); - } - } else { - debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.", - conn->connection_number); - } - } else { - debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses", - conn->connection_number); - } - // make up the timing peer info list part of the response... - // debug(1,"Create timingPeerInfoPlist"); - plist_t timingPeerInfoPlist = plist_new_dict(); - plist_t addresses = plist_new_array(); // to hold the device's interfaces - plist_array_append_item(addresses, plist_new_string(conn->self_ip_string)); - // debug(1,"self ip: \"%s\"", conn->self_ip_string); - - struct ifaddrs *addrs, *iap; - getifaddrs(&addrs); - for (iap = addrs; iap != NULL; iap = iap->ifa_next) { - // debug(1, "Interface index %d, name: \"%s\"",if_nametoindex(iap->ifa_name), - // iap->ifa_name); - if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) && - ((iap->ifa_flags & IFF_LOOPBACK) == 0)) { - char buf[INET6_ADDRSTRLEN + 1]; // +1 for a NUL - memset(buf, 0, sizeof(buf)); - if (iap->ifa_addr->sa_family == AF_INET6) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr); - inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf)); - plist_array_append_item(addresses, plist_new_string(buf)); - // debug(1, "Own address IPv6: %s", buf); - - // strncat(timing_list_message, " ", - // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - // strncat(timing_list_message, buf, - // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - - } else { - struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr); - inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf)); - plist_array_append_item(addresses, plist_new_string(buf)); - // debug(1, "Own address IPv4: %s", buf); - - // strncat(timing_list_message, " ", - // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - // strncat(timing_list_message, buf, - // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); - } - } - } - freeifaddrs(addrs); - - // debug(1,"initial timing peer command: \"%s\".", timing_list_message); - //ptp_send_control_message_string(timing_list_message); - set_client_as_ptp_clock(conn); - plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses); - plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string)); - plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist); - // get a port to use as an event port - // bind a new TCP port and get a socket - conn->local_event_port = 0; // any port - int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, - conn->self_ip_string, conn->self_scope_id, - &conn->local_event_port, &conn->event_socket); - if (err) { - die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event " - "port", - conn->connection_number, err); - } - - debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number, - conn->local_event_port); - - if (conn->rtp_event_thread != NULL) - debug(1, "previous rtp_event_thread allocation not freed, it seems."); - conn->rtp_event_thread = malloc(sizeof(pthread_t)); - if (conn->rtp_event_thread == NULL) - die("Couldn't allocate space for pthread_t"); - - pthread_create(conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn); - - plist_dict_set_item(setupResponsePlist, "eventPort", - plist_new_uint(conn->local_event_port)); - plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy - cancel_all_RTSP_threads(unspecified_stream_category, - conn->connection_number); // kill all the other listeners - - config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay - build_bonjour_strings(conn); - debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - mdns_update(NULL, secondary_txt_records); - - resp->respcode = 200; - } else { - debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.", - conn->connection_number); - } - -#ifdef CONFIG_METADATA - check_and_send_plist_metadata(messagePlist, "name", 'snam'); - check_and_send_plist_metadata(messagePlist, "deviceID", 'cdid'); - check_and_send_plist_metadata(messagePlist, "model", 'cmod'); - check_and_send_plist_metadata(messagePlist, "macAddress", 'cmac'); -#endif - } else if (conn->airplay_stream_category == ntp_stream) { - debug(1, "SETUP on Connection %d: ntp stream handling is not implemented!", - conn->connection_number, req); - warn("Shairport Sync can not handle NTP streams."); - } else if (conn->airplay_stream_category == remote_control_stream) { - debug_log_rtsp_message(2, "SETUP (no stream) \"isRemoteControlOnly\" message", req); - - // get a port to use as an event port - // bind a new TCP port and get a socket - conn->local_event_port = 0; // any port - int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, - conn->self_ip_string, conn->self_scope_id, - &conn->local_event_port, &conn->event_socket); - if (err) { - die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event " - "port", - conn->connection_number, err); - } - - debug(2, "Connection %d SETUP (RC): TCP Remote Control event port opened: %u.", - conn->connection_number, conn->local_event_port); - if (conn->rtp_event_thread != NULL) - debug(1, - "Connection %d SETUP (RC): previous rtp_event_thread allocation not freed, it " - "seems.", - conn->connection_number); - - conn->rtp_event_thread = malloc(sizeof(pthread_t)); - if (conn->rtp_event_thread == NULL) - die("Couldn't allocate space for pthread_t"); - pthread_create(conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn); - plist_dict_set_item(setupResponsePlist, "eventPort", - plist_new_uint(conn->local_event_port)); - plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); - cancel_all_RTSP_threads( - remote_control_stream, - conn->connection_number); // kill all the other remote control listeners - resp->respcode = 200; - } else { - debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.", - conn->connection_number, timingProtocolString); - warn("Shairport Sync can not handle streams of this type: \"%s\".", timingProtocolString); - } - free(timingProtocolString); - } else { - debug(1, "SETUP on Connection %d: Can't retrieve timingProtocol string in initial SETUP.", - conn->connection_number); - } - } else { - debug(1, - "SETUP on Connection %d: Unrecognised SETUP incoming message from \"%s\": no " - "timingProtocol or streams plist found.", - conn->connection_number, (const char *)conn->client_ip_string); - debug_log_rtsp_message(2, "Unrecognised SETUP incoming message.", req); - warn("Unrecognised SETUP incoming message -- ignored."); - } - } else { - debug(2, "Connection %d: SETUP on %s. A \"streams\" array has been found", - conn->connection_number, get_category_string(conn->airplay_stream_category)); - if (conn->airplay_stream_category == ptp_stream) { - // get stream[0] - plist_t stream0 = plist_array_get_item(streams, 0); - - plist_t streams_array = plist_new_array(); // to hold the ports and stuff - plist_t stream0dict = plist_new_dict(); - - // get the session key -- it must have one - - plist_t item = plist_dict_get_item(stream0, "shk"); // session key - uint64_t item_value = 0; // the length - plist_get_data_val(item, (char **)&conn->session_key, &item_value); - - // more stuff - // set up a UDP control stream and thread and a UDP or TCP audio stream and thread - - // bind a new UDP port and get a socket - conn->local_ap2_control_port = 0; // any port - err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_ap2_control_port, - &conn->ap2_control_socket); - if (err) { - die("Error %d: could not find a UDP port to use as an ap2_control port", err); - } - debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, - conn->local_ap2_control_port); - - pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn); - - // get the DACP-ID and Active Remote for remote control stuff - - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", - conn->connection_number, ar); - // get the active remote - if (conn->dacp_active_remote) // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); -#endif - } else { - debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.", - conn->connection_number); - if (conn->dacp_active_remote) { // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } - } - - ar = msg_get_header(req, "DACP-ID"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", - conn->connection_number, ar); - if (conn->dacp_id) // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); -#endif - } else { - debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", - conn->connection_number); - if (conn->dacp_id) { // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = NULL; - } - } - - // now, get the type of the stream. - item = plist_dict_get_item(stream0, "type"); - item_value = 0; - plist_get_uint_val(item, &item_value); - - switch (item_value) { - case 96: { - debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = realtime_stream; - // bind a new UDP port and get a socket - conn->local_realtime_audio_port = 0; // any port - err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_realtime_audio_port, - &conn->realtime_audio_socket); - if (err) { - die("Error %d: could not find a UDP port to use as a realtime_audio port", err); - } - debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, - conn->local_realtime_audio_port); - - pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, - (void *)conn); - - plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_realtime_audio_port)); - - conn->stream.type = ast_apple_lossless; - debug(3, "An ALAC stream has been detected."); - - // Set reasonable connection defaults - conn->stream.fmtp[0] = 96; - conn->stream.fmtp[1] = 352; - conn->stream.fmtp[2] = 0; - conn->stream.fmtp[3] = 16; - conn->stream.fmtp[4] = 40; - conn->stream.fmtp[5] = 10; - conn->stream.fmtp[6] = 14; - conn->stream.fmtp[7] = 2; - conn->stream.fmtp[8] = 255; - conn->stream.fmtp[9] = 0; - conn->stream.fmtp[10] = 0; - conn->stream.fmtp[11] = 44100; - - // set the parameters of the player (as distinct from the parameters of the decoder -- - // that's done later). - conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. - conn->input_rate = conn->stream.fmtp[11]; - conn->input_num_channels = conn->stream.fmtp[7]; - conn->input_bit_depth = conn->stream.fmtp[3]; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - debug(2, "Realtime Stream Play"); - activity_monitor_signify_activity(1); - player_prepare_to_play(conn); - player_play(conn); - - conn->rtp_running = 1; // hack! - } break; - case 103: { - debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = buffered_stream; - // get needed stuff - - // bind a new TCP port and get a socket - conn->local_buffered_audio_port = 0; // any port - err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_buffered_audio_port, - &conn->buffered_audio_socket); - if (err) { - die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " - "buffered_audio port", - conn->connection_number, err); - } - - debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, - conn->local_buffered_audio_port); - - // hack. - conn->max_frames_per_packet = 352; // number of audio frames per packet. - conn->input_rate = 44100; // we are stuck with this for the moment. - conn->input_num_channels = 2; - conn->input_bit_depth = 16; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - activity_monitor_signify_activity(1); - player_prepare_to_play( - conn); // get capabilities of DAC before creating the buffered audio thread - - pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor, - (void *)conn); - - plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_buffered_audio_port)); - plist_dict_set_item(stream0dict, "audioBufferSize", - plist_new_uint(conn->ap2_audio_buffer_size)); - - // this should be cancelled by an activity_monitor_signify_activity(1) - // call in the SETRATEANCHORI handler, which should come up right away - activity_monitor_signify_activity(0); - player_play(conn); - - conn->rtp_running = 1; // hack! - } break; - default: - debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", - conn->connection_number, item_value); - debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); - } - - plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); - - plist_array_append_item(streams_array, stream0dict); - plist_dict_set_item(setupResponsePlist, "streams", streams_array); - resp->respcode = 200; - - } else if (conn->airplay_stream_category == remote_control_stream) { - debug(2, "Connection %d (RC): SETUP: Remote Control Stream received from %s.", - conn->connection_number, conn->client_ip_string); - debug_log_rtsp_message(2, "Remote Control Stream SETUP incoming message", req); - /* - // get a port to use as an data port - // bind a new TCP port and get a socket - conn->local_data_port = 0; // any port - int err = - bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_data_port, &conn->data_socket); - if (err) { - die("SETUP on Connection %d (RC): Error %d: could not find a TCP port to use as a data " - "port", - conn->connection_number, err); - } - - debug(1, "Connection %d SETUP (RC): TCP Remote Control data port opened: %u.", - conn->connection_number, conn->local_data_port); - if (conn->rtp_data_thread != NULL) - debug(1, "Connection %d SETUP (RC): previous rtp_data_thread allocation not freed, it - seems.", conn->connection_number); conn->rtp_data_thread = malloc(sizeof(pthread_t)); if - (conn->rtp_data_thread == NULL) die("Couldn't allocate space for pthread_t"); - - pthread_create(conn->rtp_data_thread, NULL, &rtp_data_receiver, (void *)conn); - - plist_t coreResponseDict = plist_new_dict(); - plist_dict_set_item(coreResponseDict, "streamID", plist_new_uint(1)); - plist_dict_set_item(coreResponseDict, "type", plist_new_uint(130)); - plist_dict_set_item(coreResponseDict, "dataPort", plist_new_uint(conn->local_data_port)); - - plist_t coreResponseArray = plist_new_array(); - plist_array_append_item(coreResponseArray, coreResponseDict); - plist_dict_set_item(setupResponsePlist, "streams", coreResponseArray); - */ - resp->respcode = 200; - } else { - debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.", - conn->connection_number); - } - } - - if (resp->respcode == 200) { - plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength); - plist_free(setupResponsePlist); - msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - } - plist_free(messagePlist); - if (clientNameString != NULL) - free(clientNameString); - debug_log_rtsp_message(2, " SETUP response", resp); -} -#endif - -void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: SETUP", conn->connection_number); - resp->respcode = 451; // invalid arguments -- expect them - if (have_play_lock(conn)) { - uint16_t cport, tport; - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - debug(2, "Connection %d: SETUP: Active-Remote string seen: \"%s\".", conn->connection_number, - ar); - // get the active remote - if (conn->dacp_active_remote) // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); -#endif - } else { - debug(2, "Connection %d: SETUP: Note: no Active-Remote information seen.", - conn->connection_number); - if (conn->dacp_active_remote) { // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } - } - - ar = msg_get_header(req, "DACP-ID"); - if (ar) { - debug(2, "Connection %d: SETUP: DACP-ID string seen: \"%s\".", conn->connection_number, ar); - if (conn->dacp_id) // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); -#endif - } else { - debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.", - conn->connection_number); - if (conn->dacp_id) { // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = NULL; - } - } - - char *hdr = msg_get_header(req, "Transport"); - if (hdr) { - char *p; - p = strstr(hdr, "control_port="); - if (p) { - p = strchr(p, '=') + 1; - cport = atoi(p); - - p = strstr(hdr, "timing_port="); - if (p) { - p = strchr(p, '=') + 1; - tport = atoi(p); - - if (conn->rtp_running) { - if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) { - warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) " - "or " - "timing (old %u, new " - "%u) ports! This is probably fatal!", - conn->connection_number, conn->remote_control_port, cport, - conn->remote_timing_port, tport); - } else { - warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing " - "(%u) " - "ports. This is " - "probably not fatal.", - conn->connection_number, conn->remote_control_port, conn->remote_timing_port); - } - } else { - rtp_setup(&conn->local, &conn->remote, cport, tport, conn); - } - if (conn->local_audio_port != 0) { - - char resphdr[256] = ""; - snprintf(resphdr, sizeof(resphdr), - "RTP/AVP/" - "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" - "timing_port=%d;server_" - "port=%d", - conn->local_control_port, conn->local_timing_port, conn->local_audio_port); - - msg_add_header(resp, "Transport", resphdr); - - msg_add_header(resp, "Session", "1"); - - resp->respcode = 200; // it all worked out okay - debug(2, - "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: " - "%d, Timing: %d and Audio: %d.", - conn->connection_number, conn->dacp_id, &conn->client_ip_string, - &conn->self_ip_string, conn->local_control_port, conn->local_timing_port, - conn->local_audio_port); - - } else { - debug(1, "Connection %d: SETUP seems to specify a null audio port.", - conn->connection_number); - } - } else { - debug(1, "Connection %d: SETUP doesn't specify a timing_port.", conn->connection_number); - } - } else { - debug(1, "Connection %d: SETUP doesn't specify a control_port.", conn->connection_number); - } - } else { - debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number); - } - if (resp->respcode == 200) { -#ifdef CONFIG_METADATA - send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1); - send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1); -#endif - } else { - debug(1, "Connection %d: SETUP error -- releasing the player lock.", conn->connection_number); - release_play_lock(conn); - } - - } else { - warn("Connection %d SETUP received without having the player (no ANNOUNCE?)", - conn->connection_number); - } -} - -/* -static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(1, "Connection thread %d: IGNORE", conn->connection_number); - resp->respcode = 200; -} -*/ - -void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - - char *cp = req->content; - int cp_left = req->contentlength; - /* - int k = cp_left; - if (k>max_bytes) - k = max_bytes; - for (i = 0; i < k; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", cp[i]); - debug(1, "handle_set_parameter_parameter: \"%s\".",buf); - */ - - char *next; - while (cp_left && cp) { - next = nextline(cp, cp_left); - // note: "next" will return NULL if there is no \r or \n or \r\n at the end of this - // but we are always guaranteed that if cp is not null, it will be pointing to something - // NUL-terminated - - if (next) - cp_left -= (next - cp); - else - cp_left = 0; - - if (!strncmp(cp, "volume: ", strlen("volume: "))) { - float volume = atof(cp + strlen("volume: ")); - debug(2, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number, - volume); - // if we are playing, go ahead and change the volume -#ifdef CONFIG_DBUS_INTERFACE - if (dbus_service_is_running()) { - shairport_sync_set_volume(shairportSyncSkeleton, volume); - } else { -#endif - lock_player(); - if (playing_conn == conn) - player_volume(volume, conn); - unlock_player(); -#ifdef CONFIG_DBUS_INTERFACE - } -#endif - } else if (strncmp(cp, "progress: ", strlen("progress: ")) == - 0) { // this can be sent even when metadata is not solicited - -#ifdef CONFIG_METADATA - char *progress = cp + strlen("progress: "); - // debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per - // second - send_ssnc_metadata('prgr', progress, strlen(progress), 1); -#endif - - } else { - debug(1, "Connection %d, unrecognised parameter: \"%s\" (%d)\n", conn->connection_number, cp, - strlen(cp)); - } - cp = next; - } -} - -#ifdef CONFIG_METADATA -// Metadata is not used by shairport-sync. -// Instead we send all metadata to a fifo pipe, so that other apps can listen to -// the pipe and use the metadata. - -// We use two 4-character codes to identify each piece of data and we send the -// data itself, if any, -// in base64 form. - -// The first 4-character code, called the "type", is either: -// 'core' for all the regular metadadata coming from iTunes, etc., or -// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync -// itself, such as -// start/end delimiters, etc. - -// For 'core' metadata, the second 4-character code is the 4-character metadata -// code coming from -// iTunes etc. -// For 'ssnc' metadata, the second 4-character code is used to distinguish the -// messages. - -// Cover art is not tagged in the same way as other metadata, it seems, so is -// sent as an 'ssnc' type -// metadata message with the code 'PICT' -// Here are the 'ssnc' codes defined so far: -// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the -// first few bytes to see -// which. -// 'abeg' -- active mode entered. No arguments -// 'aend' -- active mode exited. No arguments -// 'pbeg' -- play stream begin. No arguments -// 'pend' -- play stream end. No arguments -// 'pfls' -- play stream flush. The argument is an unsigned 32-bit -// frame number. It seems that all frames up to but not -// including this frame are to be flushed. -// -// 'prsm' -- play stream resume. No arguments. (deprecated) -// 'paus' -- buffered audio stream paused. No arguments. -// 'pres' -- buffered audio stream resumed. No arguments. -// 'pffr' -- the first frame of a play session has been received and has been validly -// timed. -// 'pvol' -- play volume. The volume is sent as a string -- -// "airplay_volume,volume,lowest_volume,highest_volume" -// volume, lowest_volume and highest_volume are given in dB. -// The "airplay_volume" is what's sent to the player, and is from -// 0.00 down to -30.00, -// with -144.00 meaning mute. -// This is linear on the volume control slider of iTunes or iOS -// AirPlay. -// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP -// timestamps for the start -// of the current play sequence, the current play point and the end of the -// play sequence. -// I guess the timestamps wrap at 2^32. -// 'mdst' -- a sequence of metadata is about to start; will have, as data, -// the rtptime associated with the metadata, if available -// 'mden' -- a sequence of metadata has ended; will have, as data, the -// rtptime associated with the metadata, if available -// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime -// associated with the picture, if available -// 'pcen' -- a picture has been sent; will have, as data, the rtptime -// associated with the metadata, if available -// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session. -// Specifically, it's the "X-Apple-Client-Name" string -// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play -// session. Specifically, it's the "User-Agent" string -// The next two two tokens are to facilitate remote control of the source. -// There is some information at http://nto.github.io/AirPlay.html about -// remote control of the source. -// -// 'daid' -- this is the source's DACP-ID (if it has one -- it's not -// guaranteed), useful if you want to remotely control the source. Use this -// string to identify the source's remote control on the network. -// 'acre' -- this is the source's Active-Remote token, necessary if you want -// to send commands to the source's remote control (if it has one). -// `clip` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon -// as the client has exclusive access to the player and after any existing -// play session has been interrupted and terminated. -// `conn` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. -// It is sent as soon as the client requests access to the player. -// If Shairport Sync is already playing, this message is sent before the current -// play session is stopped. -// `svip` -- the payload is the IP number of the server, i.e. the player itself. -// Can be an IPv4 or an IPv6 number. -// `disc` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. -// It is sent when a client has been disconnected. -// `dapo` -- the payload is the port number (as text) on the server to which remote -// control commands should be sent. It is 3689 for iTunes but varies for iOS devices. -// `` - -// A special sub-protocol is used for sending large data items over UDP -// If the payload exceeded 4 MB, it is chunked using the following format: -// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data. -// Notice that the number of items is different to the standard - -// including a simple base64 encoder to minimise malloc/free activity - -// From Stack Overflow, with thanks: -// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c -// minor mods to make independent of C99. -// more significant changes make it not malloc memory -// needs to initialise the docoding table first - -// add _so to end of name to avoid confusion with polarssl's implementation - -static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; - -static size_t mod_table[] = {0, 2, 1}; - -// pass in a pointer to the data, its length, a pointer to the output buffer and -// a pointer to an int -// containing its maximum length -// the actual length will be returned. - -char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, - size_t *output_length) { - - size_t calculated_output_length = 4 * ((input_length + 2) / 3); - if (calculated_output_length > *output_length) - return (NULL); - *output_length = calculated_output_length; - - size_t i, j; - for (i = 0, j = 0; i < input_length;) { - - uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; - uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; - encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; - } - - for (i = 0; i < mod_table[input_length % 3]; i++) - encoded_data[*output_length - 1 - i] = '='; - - return encoded_data; -} - -// with thanks! -// - -static int fd = -1; -// static int dirty = 0; - -pc_queue metadata_queue; -#define metadata_queue_size 500 -metadata_package metadata_queue_items[metadata_queue_size]; -pthread_t metadata_thread; - -#ifdef CONFIG_METADATA_HUB -pc_queue metadata_hub_queue; -#define metadata_hub_queue_size 500 -metadata_package metadata_hub_queue_items[metadata_hub_queue_size]; -pthread_t metadata_hub_thread; -#endif - -#ifdef CONFIG_MQTT -pc_queue metadata_mqtt_queue; -#define metadata_mqtt_queue_size 500 -metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size]; -pthread_t metadata_mqtt_thread; -#endif - -static int metadata_sock = -1; -static struct sockaddr_in metadata_sockaddr; -static char *metadata_sockmsg; -pc_queue metadata_multicast_queue; -#define metadata_multicast_queue_size 500 -metadata_package metadata_multicast_queue_items[metadata_queue_size]; -pthread_t metadata_multicast_thread; - -void metadata_create_multicast_socket(void) { - if (config.metadata_enabled == 0) - return; - - // Unlike metadata pipe, socket is opened once and stays open, - // so we can call it in create - if (config.metadata_sockaddr && config.metadata_sockport) { - metadata_sock = socket(AF_INET, SOCK_DGRAM, 0); - if (metadata_sock < 0) { - debug(1, "Could not open metadata socket"); - } else { - int buffer_size = METADATA_SNDBUF; - setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); - bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr)); - metadata_sockaddr.sin_family = AF_INET; - metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr); - metadata_sockaddr.sin_port = htons(config.metadata_sockport); - metadata_sockmsg = malloc(config.metadata_sockmsglength); - if (metadata_sockmsg) { - memset(metadata_sockmsg, 0, config.metadata_sockmsglength); - } else { - die("Could not malloc metadata multicast socket buffer"); - } - } - } -} - -void metadata_delete_multicast_socket(void) { - if (config.metadata_enabled == 0) - return; - shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer - close(metadata_sock); - if (metadata_sockmsg) - free(metadata_sockmsg); -} - -void metadata_open(void) { - if (config.metadata_enabled == 0) - return; - - size_t pl = strlen(config.metadata_pipename) + 1; - - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - - fd = try_to_open_pipe_for_writing(path); - free(path); -} - -static void metadata_close(void) { - if (fd < 0) - return; - close(fd); - fd = -1; -} - -void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, - // length); - if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { - char *ptr = metadata_sockmsg; - uint32_t v; - v = htonl(type); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(code); - memcpy(ptr, &v, 4); - ptr += 4; - memcpy(ptr, data, length); - sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, - sizeof(metadata_sockaddr)); - } else if (metadata_sock >= 0) { - // send metadata in numbered chunks using the protocol: - // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data) - - uint32_t chunk_ix = 0; - if (config.metadata_sockmsglength == 24) - die("A divide by zero almost occurred (config.metadata_sockmsglength = 24)."); - uint32_t chunk_total = length / (config.metadata_sockmsglength - 24); - if (chunk_total * (config.metadata_sockmsglength - 24) < length) { - chunk_total++; - } - uint32_t remaining = length; - uint32_t v; - char *data_crsr = data; - do { - char *ptr = metadata_sockmsg; - memcpy(ptr, "ssncchnk", 8); - ptr += 8; - v = htonl(chunk_ix); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(chunk_total); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(type); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(code); - memcpy(ptr, &v, 4); - ptr += 4; - size_t datalen = remaining; - if (datalen > config.metadata_sockmsglength - 24) { - datalen = config.metadata_sockmsglength - 24; - } - memcpy(ptr, data_crsr, datalen); - data_crsr += datalen; - sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0, - (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); - chunk_ix++; - remaining -= datalen; - if (remaining == 0) - break; - } while (1); - } -} - -void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length); - int ret = 0; - // readers may go away and come back - - if (fd < 0) - metadata_open(); - if (fd < 0) - return; - char thestring[1024]; - snprintf(thestring, 1024, "%x%x%u", type, code, - length); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 1",ret); - return; - } - if ((data != NULL) && (length > 0)) { - snprintf(thestring, 1024, "\n\n"); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 2",ret); - return; - } - // here, we write the data in base64 form using our nice base64 encoder - // but, we break it into lines of 76 output characters, except for the last - // one. - // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time - size_t remaining_count = length; - char *remaining_data = data; - // size_t towrite_count; - char outbuf[76]; - while ((remaining_count) && (ret >= 0)) { - size_t towrite_count = remaining_count; - if (towrite_count > 57) - towrite_count = 57; - size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) == - NULL) - debug(1, "Error encoding base64 data."); - // debug(1,"Remaining count: %d ret: %d, outbuf_size: - // %d.",remaining_count,ret,outbuf_size); - // ret = non_blocking_write(fd, outbuf, outbuf_size); - ret = write(fd, outbuf, outbuf_size); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 3",ret); - return; - } - remaining_data += towrite_count; - remaining_count -= towrite_count; - } - snprintf(thestring, 1024, ""); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 4",ret); - return; - } - } - snprintf(thestring, 1024, "\n"); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 5",ret); - return; - } -} - -void metadata_pack_cleanup_function(void *arg) { - // debug(1, "metadata_pack_cleanup_function called"); - metadata_package *pack = (metadata_package *)arg; - if (pack->carrier) - msg_free(&pack->carrier); // release the message - else if (pack->data) - free(pack->data); - // debug(1, "metadata_pack_cleanup_function exit"); -} - -void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_thread_cleanup_function called"); - metadata_close(); - pc_queue_delete(&metadata_queue); -} - -void *metadata_thread_function(__attribute__((unused)) void *ignore) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), - metadata_queue_size, "pipe"); - metadata_create_multicast_socket(); - metadata_package pack; - pthread_cleanup_push(metadata_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.metadata_enabled) { - if (pack.carrier) { - debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, - pack.length, pack.carrier->index_number); - } else { - debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } - metadata_process(pack.type, pack.code, pack.data, pack.length); - debug(3, " pipe: done."); - } - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} - -void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_multicast_thread_cleanup_function called"); - metadata_delete_multicast_socket(); - pc_queue_delete(&metadata_multicast_queue); -} - -void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, - sizeof(metadata_package), metadata_multicast_queue_size, "multicast"); - metadata_create_multicast_socket(); - metadata_package pack; - pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_multicast_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.metadata_enabled) { - if (pack.carrier) { - debug(3, - " multicast: type " - "%x, code %x, length %u, message %d.", - pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, - " multicast: type " - "%x, code %x, length %u.", - pack.type, pack.code, pack.length); - } - metadata_multicast_process(pack.type, pack.code, pack.data, pack.length); - debug(3, - " multicast: done."); - } - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} - -#ifdef CONFIG_METADATA_HUB -void metadata_hub_close(void) {} - -void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_hub_thread_cleanup_function called"); - metadata_hub_close(); - pc_queue_delete(&metadata_hub_queue); -} - -void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package), - metadata_hub_queue_size, "hub"); - metadata_package pack; - pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_hub_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (pack.carrier) { - debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, - pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, - pack.length); - } - metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(3, " hub: done."); - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} -#endif - -#ifdef CONFIG_MQTT -void metadata_mqtt_close(void) {} - -void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_mqtt_thread_cleanup_function called"); - metadata_mqtt_close(); - pc_queue_delete(&metadata_mqtt_queue); - // debug(2, "metadata_mqtt_thread_cleanup_function done"); -} - -void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { - // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package), - metadata_mqtt_queue_size, "mqtt"); - metadata_package pack; - pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_mqtt_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.mqtt_enabled) { - if (pack.carrier) { - debug(3, - " mqtt: type %x, code %x, length %u, message " - "%d.", - pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " mqtt: type %x, code %x, length %u.", - pack.type, pack.code, pack.length); - } - mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(3, " mqtt: done."); - } - - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} -#endif - -void metadata_init(void) { - if (config.metadata_enabled) { - // create the metadata pipe, if necessary - size_t pl = strlen(config.metadata_pipename) + 1; - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - mode_t oldumask = umask(000); - if (mkfifo(path, 0666) && errno != EEXIST) - die("Could not create metadata pipe \"%s\".", path); - umask(oldumask); - debug(1, "metadata pipe name is \"%s\".", path); - - // try to open it - fd = try_to_open_pipe_for_writing(path); - // we check that it's not a "real" error. From the "man 2 open" page: - // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO - // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - } - free(path); - if (pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL) != 0) - debug(1, "Failed to create metadata thread!"); - - if (pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, - NULL) != 0) - debug(1, "Failed to create metadata multicast thread!"); - } -#ifdef CONFIG_METADATA_HUB - if (pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL) != 0) - debug(1, "Failed to create metadata hub thread!"); -#endif -#ifdef CONFIG_MQTT - if (pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL) != 0) - debug(1, "Failed to create metadata mqtt thread!"); -#endif - metadata_running = 1; -} - -void metadata_stop(void) { - if (metadata_running) { - debug(2, "metadata_stop called."); -#ifdef CONFIG_MQTT - // debug(2, "metadata stop mqtt thread."); - pthread_cancel(metadata_mqtt_thread); - pthread_join(metadata_mqtt_thread, NULL); - // debug(2, "metadata stop mqtt done."); -#endif -#ifdef CONFIG_METADATA_HUB - // debug(2, "metadata stop hub thread."); - pthread_cancel(metadata_hub_thread); - pthread_join(metadata_hub_thread, NULL); - // debug(2, "metadata stop hub done."); -#endif - if (config.metadata_enabled) { - // debug(2, "metadata stop multicast thread."); - if (metadata_multicast_thread) { - pthread_cancel(metadata_multicast_thread); - pthread_join(metadata_multicast_thread, NULL); - // debug(2, "metadata stop multicast done."); - } - if (metadata_thread) { - // debug(2, "metadata stop metadata_thread thread."); - pthread_cancel(metadata_thread); - pthread_join(metadata_thread, NULL); - // debug(2, "metadata_stop finished successfully."); - } - } - } -} - -int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *data, - uint32_t length, rtsp_message *carrier, int block) { - - // clang-format off - // parameters: - // type, - // code, - // pointer to data or NULL, - // length of data or NULL, - // the rtsp_message or NULL, - - // the rtsp_message is sent for 'core' messages, because it contains the data - // and must not be freed until the data has been read. - // So, it is passed to send_metadata to be retained, sent to the thread where metadata - // is processed and released (and probably freed). - - // The rtsp_message is also sent for certain non-'core' messages. - - // The reading of the parameters is a bit complex: - // If the rtsp_message field is non-null, then it represents an rtsp_message - // and the data pointer is assumed to point to something within it. - // The reference counter of the rtsp_message is incremented here and - // should be decremented by the metadata handler when finished. - // If the reference count reduces to zero, the message will be freed. - - // If the rtsp_message is NULL, then if the pointer is non-null then the data it - // points to, of the length specified, is memcpy'd and passed to the metadata - // handler. The handler should free it when done. - - // If the rtsp_message is NULL and the pointer is also NULL, nothing further - // is done. - // clang-format on - - metadata_package pack; - pack.type = type; - pack.code = code; - pack.length = length; - pack.carrier = carrier; - pack.data = data; - if (pack.carrier) { - msg_retain(pack.carrier); - } else { - if (data) - pack.data = memdup(data, length); // only if it's not a null - } - int rc = pc_queue_add_item(queue, &pack, block); - if (rc != 0) { - if (pack.carrier) { - if (rc == EWOULDBLOCK) - debug(2, - "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, " - "length %u, message %d.", - queue->name, pack.type, pack.code, pack.data, pack.length, - pack.carrier->index_number); - msg_free(&pack.carrier); - } else { - if (rc == EWOULDBLOCK) - debug( - 2, - "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", - queue->name, pack.type, pack.code, pack.data, pack.length); - if (pack.data) - free(pack.data); - } - } - return rc; -} - -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, - int block) { - int rc = 0; - if (config.metadata_enabled) { - rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); - rc = - send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block); - } - -#ifdef CONFIG_METADATA_HUB - rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); -#endif - -#ifdef CONFIG_MQTT - rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block); -#endif - - return rc; -} - -static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn, - rtsp_message *req, - __attribute__((unused)) rtsp_message *resp) { - char *cp = req->content; - unsigned int cl = req->contentlength; - - unsigned int off = 8; - - uint32_t itag, vl; - while (off < cl) { - // pick up the metadata tag as an unsigned longint - memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ - itag = ntohl(itag); - off += sizeof(uint32_t); - - // pick up the length of the data - memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ - vl = ntohl(vl); - off += sizeof(uint32_t); - - // pass the data over - if (vl == 0) - send_metadata('core', itag, NULL, 0, NULL, 1); - else - send_metadata('core', itag, (char *)(cp + off), vl, req, 1); - - // move on to the next item - off += vl; - } -} - -#endif - -static void handle_get_parameter(__attribute__((unused)) 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, "Connection %d: Current volume (%.6f) requested", conn->connection_number, - config.airplay_volume); - char *p = malloc(128); // will be automatically deallocated with the response is deleted - if (p) { - resp->content = p; - resp->contentlength = snprintf(p, 128, "\r\nvolume: %.6f\r\n", config.airplay_volume); - } else { - debug(1, "Couldn't allocate space for a response."); - } - } - resp->respcode = 200; -} - -static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: SET_PARAMETER", conn->connection_number); - // if (!req->contentlength) - // debug(1, "received empty SET_PARAMETER request."); - - // debug_print_msg_headers(1,req); - - char *ct = msg_get_header(req, "Content-Type"); - - if (ct) { - // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); - -#ifdef CONFIG_METADATA - // It seems that the rtptime of the message is used as a kind of an ID that - // can be used - // to link items of metadata, including pictures, that refer to the same - // entity. - // If they refer to the same item, they have the same rtptime. - // So we send the rtptime before and after both the metadata items and the - // picture item - // get the rtptime - char *p = NULL; - char *hdr = msg_get_header(req, "RTP-Info"); - - if (hdr) { - p = strstr(hdr, "rtptime="); - if (p) { - p = strchr(p, '='); - } - } - - // not all items have RTP-time stuff in them, which is okay - - if (!strncmp(ct, "application/x-dmap-tagged", 25)) { - debug(3, "received metadata tags in SET_PARAMETER request."); - if (p == NULL) - debug(1, "Missing RTP-Time info for metadata"); - if (p) - send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting - else - send_metadata('ssnc', 'mdst', NULL, 0, NULL, - 0); // metadata starting, if rtptime is not available - - handle_set_parameter_metadata(conn, req, resp); - - if (p) - send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending - else - send_metadata('ssnc', 'mden', NULL, 0, NULL, - 0); // metadata starting, if rtptime is not available - - } else if (!strncmp(ct, "image", 5)) { - // Some server simply ignore the md field from the TXT record. If The - // config says 'please, do not include any cover art', we are polite and - // do not write them to the pipe. - if (config.get_coverart) { - // debug(1, "received image in SET_PARAMETER request."); - // note: the image/type tag isn't reliable, so it's not being sent - // -- best look at the first few bytes of the image - if (p == NULL) - debug(1, "Missing RTP-Time info for picture item"); - if (p) - send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting - else - send_metadata('ssnc', 'pcst', NULL, 0, NULL, - 0); // picture starting, if rtptime is not available - - send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); - - if (p) - send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending - else - send_metadata('ssnc', 'pcen', NULL, 0, NULL, - 0); // picture ending, if rtptime is not available - } else { - debug(1, "Ignore received picture item (include_cover_art = no)."); - } - } else -#endif - if (!strncmp(ct, "text/parameters", 15)) { - // debug(2, "received parameters in SET_PARAMETER request."); - handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress - } else { - debug(1, "Connection %d: received unknown Content-Type \"%s\" in SET_PARAMETER request.", - conn->connection_number, ct); - debug_print_msg_headers(1, req); - } - } else { - debug(1, "Connection %d: missing Content-Type header in SET_PARAMETER request.", - conn->connection_number); - } - resp->respcode = 200; -} - -static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: ANNOUNCE", conn->connection_number); - int get_play_status = get_play_lock(conn, config.allow_session_interruption); - if (get_play_status != -1) { - debug(3, "Connection %d: ANNOUNCE has acquired play lock.", conn->connection_number); - - // now, if this new session did not break in, then it's okay to reset the next UDP ports - // to the start of the range - - if (get_play_status == 1) { // will be zero if it wasn't waiting to break in - resetFreeUDPPort(); - } - - /* - { - char *cp = req->content; - int cp_left = req->contentlength; - while (cp_left > 1) { - if (strlen(cp) != 0) - debug(1,">>>>>> %s", cp); - cp += strlen(cp) + 1; - cp_left -= strlen(cp) + 1; - } - } - */ - // In AirPlay 2, an ANNOUNCE signifies the start of an AirPlay 1 session. -#ifdef CONFIG_AIRPLAY_2 - conn->airplay_type = ap_1; - conn->timing_type = ts_ntp; - debug(1, "Connection %d: Classic AirPlay connection from %s:%u to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - conn->self_ip_string, conn->self_rtsp_port); -#endif - - conn->stream.type = ast_unknown; - resp->respcode = 456; // 456 - Header Field Not Valid for Resource - char *pssid = NULL; - char *paesiv = NULL; - char *prsaaeskey = NULL; - char *pfmtp = NULL; - char *pminlatency = NULL; - char *pmaxlatency = NULL; - // char *pAudioMediaInfo = NULL; - char *pUncompressedCDAudio = NULL; - char *cp = req->content; - int cp_left = req->contentlength; - char *next; - while (cp_left && cp) { - next = nextline(cp, cp_left); - cp_left -= next - cp; - - if (!strncmp(cp, "a=rtpmap:96 L16/44100/2", strlen("a=rtpmap:96 L16/44100/2"))) - pUncompressedCDAudio = cp + strlen("a=rtpmap:96 L16/44100/2"); - - // if (!strncmp(cp, "m=audio", strlen("m=audio"))) - // pAudioMediaInfo = cp + strlen("m=audio"); - - if (!strncmp(cp, "o=iTunes", strlen("o=iTunes"))) - pssid = cp + strlen("o=iTunes"); - - if (!strncmp(cp, "a=fmtp:", strlen("a=fmtp:"))) - pfmtp = cp + strlen("a=fmtp:"); - - if (!strncmp(cp, "a=aesiv:", strlen("a=aesiv:"))) - paesiv = cp + strlen("a=aesiv:"); - - if (!strncmp(cp, "a=rsaaeskey:", strlen("a=rsaaeskey:"))) - prsaaeskey = cp + strlen("a=rsaaeskey:"); - - if (!strncmp(cp, "a=min-latency:", strlen("a=min-latency:"))) - pminlatency = cp + strlen("a=min-latency:"); - - if (!strncmp(cp, "a=max-latency:", strlen("a=max-latency:"))) - pmaxlatency = cp + strlen("a=max-latency:"); - - cp = next; - } - - if (pUncompressedCDAudio) { - debug(2, "An uncompressed PCM stream has been detected."); - conn->stream.type = ast_uncompressed; - conn->max_frames_per_packet = 352; // number of audio frames per packet. - conn->input_rate = 44100; - conn->input_num_channels = 2; - conn->input_bit_depth = 16; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - - /* - int y = strlen(pAudioMediaInfo); - if (y > 0) { - char obf[4096]; - if (y > 4096) - y = 4096; - char *p = pAudioMediaInfo; - char *obfp = obf; - int obfc; - for (obfc = 0; obfc < y; obfc++) { - snprintf(obfp, 3, "%02X", (unsigned int)*p); - p++; - obfp += 2; - }; - *obfp = 0; - debug(1, "AudioMediaInfo: \"%s\".", obf); - } - */ - } - - if (pssid) { - uint32_t ssid = uatoi(pssid); - debug(3, "Synchronisation Source Identifier: %08X,%u", ssid, ssid); - } - - if (pminlatency) { - conn->minimum_latency = atoi(pminlatency); - debug(3, "Minimum latency %d specified", conn->minimum_latency); - } - - if (pmaxlatency) { - conn->maximum_latency = atoi(pmaxlatency); - debug(3, "Maximum latency %d specified", conn->maximum_latency); - } - - if ((paesiv == NULL) && (prsaaeskey == NULL)) { - // debug(1,"Unencrypted session requested?"); - conn->stream.encrypted = 0; - } else if ((paesiv != NULL) && (prsaaeskey != NULL)){ - conn->stream.encrypted = 1; - // debug(1,"Encrypted session requested"); - } else { - warn("Invalid Announce message -- missing paesiv or prsaaeskey."); - goto out; - } - if (conn->stream.encrypted) { - int len, keylen; - uint8_t *aesiv = base64_dec(paesiv, &len); - if (len != 16) { - warn("client announced aeskey of %d bytes, wanted 16", len); - free(aesiv); - goto out; - } - memcpy(conn->stream.aesiv, aesiv, 16); - free(aesiv); - - uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); - uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); - free(rsaaeskey); - if (keylen != 16) { - warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); - free(aeskey); - goto out; - } - memcpy(conn->stream.aeskey, aeskey, 16); - free(aeskey); - } - - if (pfmtp) { - conn->stream.type = ast_apple_lossless; - debug(3, "An ALAC stream has been detected."); - - // Set reasonable connection defaults - conn->stream.fmtp[0] = 96; - conn->stream.fmtp[1] = 352; - conn->stream.fmtp[2] = 0; - conn->stream.fmtp[3] = 16; - conn->stream.fmtp[4] = 40; - conn->stream.fmtp[5] = 10; - conn->stream.fmtp[6] = 14; - conn->stream.fmtp[7] = 2; - conn->stream.fmtp[8] = 255; - conn->stream.fmtp[9] = 0; - conn->stream.fmtp[10] = 0; - conn->stream.fmtp[11] = 44100; - - unsigned int i = 0; - unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); - char *found; - while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) { - conn->stream.fmtp[i++] = atoi(found); - } - // here we should check the sanity of the fmtp values - // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) - // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); - - // set the parameters of the player (as distinct from the parameters of the decoder -- that's - // done later). - conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. - conn->input_rate = conn->stream.fmtp[11]; - conn->input_num_channels = conn->stream.fmtp[7]; - conn->input_bit_depth = conn->stream.fmtp[3]; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - } - - if (conn->stream.type == ast_unknown) { - warn("Can not process the following ANNOUNCE message:"); - // print each line of the request content - // the problem is that nextline has replace all returns, newlines, etc. by - // NULLs - char *cp = req->content; - int cp_left = req->contentlength; - while (cp_left > 1) { - if (strlen(cp) != 0) - warn(" %s", cp); - cp += strlen(cp) + 1; - cp_left -= strlen(cp) + 1; - } - goto out; - } - - char *hdr = msg_get_header(req, "X-Apple-Client-Name"); - if (hdr) { - debug(1, "Play connection from device named \"%s\" on RTSP conversation thread %d.", hdr, - conn->connection_number); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'snam', hdr, strlen(hdr), req, 1); -#endif - } - hdr = msg_get_header(req, "User-Agent"); - if (hdr) { - conn->UserAgent = strdup(hdr); - debug(2, "Play connection from user agent \"%s\" on RTSP conversation thread %d.", hdr, - conn->connection_number); - // if the user agent is AirPlay and has a version number of 353 or less (from iOS 11.1,2) - // use the older way of calculating the latency - - char *p = strstr(hdr, "AirPlay"); - if (p) { - p = strchr(p, '/'); - if (p) { - conn->AirPlayVersion = atoi(p + 1); - debug(2, "AirPlay version %d detected.", conn->AirPlayVersion); - } - } - -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1); -#endif - } - resp->respcode = 200; - } else { - resp->respcode = 453; - debug(1, "Connection %d: ANNOUNCE failed because another connection is already playing.", - conn->connection_number); - } - -out: - if (resp->respcode != 200 && resp->respcode != 453) { - debug(1, "Connection %d: Error in handling ANNOUNCE. Unlocking the play lock.", - conn->connection_number); - release_play_lock(conn); - } -} - -#ifdef CONFIG_AIRPLAY_2 -static struct method_handler { - char *method; - void (*ap1_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 1 - void (*ap2_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 2 -} method_handlers[] = {{"OPTIONS", handle_options, handle_options}, - {"ANNOUNCE", handle_announce, handle_announce}, - {"FLUSH", handle_flush, handle_flush}, - {"TEARDOWN", handle_teardown, handle_teardown_2}, - {"SETUP", handle_setup, handle_setup_2}, - {"GET_PARAMETER", handle_get_parameter, handle_get_parameter}, - {"SET_PARAMETER", handle_set_parameter, handle_set_parameter}, - {"RECORD", handle_record, handle_record_2}, - {"GET", handle_get, handle_get}, - {"POST", handle_post, handle_post}, - {"SETPEERS", handle_unimplemented_ap1, handle_setpeers}, - {"SETRATEANCHORTI", handle_unimplemented_ap1, handle_setrateanchori}, - {"FLUSHBUFFERED", handle_unimplemented_ap1, handle_flushbuffered}, - {"SETRATE", handle_unimplemented_ap1, handle_setrate}, - {NULL, NULL, NULL}}; -#else -static struct method_handler { - char *method; - void (*handler)(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp); // for AirPlay 1 only -} method_handlers[] = {{"OPTIONS", handle_options}, - {"GET", handle_get}, - {"POST", handle_post}, - {"ANNOUNCE", handle_announce}, - {"FLUSH", handle_flush}, - {"TEARDOWN", handle_teardown}, - {"SETUP", handle_setup}, - {"GET_PARAMETER", handle_get_parameter}, - {"SET_PARAMETER", handle_set_parameter}, - {"RECORD", handle_record}, - {NULL, NULL}}; -#endif - -static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { - char *hdr = msg_get_header(req, "Apple-Challenge"); - if (!hdr) - return; - SOCKADDR fdsa; - socklen_t sa_len = sizeof(fdsa); - getsockname(fd, (struct sockaddr *)&fdsa, &sa_len); - - int chall_len; - uint8_t *chall = base64_dec(hdr, &chall_len); - if (chall == NULL) - die("null chall in apple_challenge"); - uint8_t buf[48], *bp = buf; - int i; - memset(buf, 0, sizeof(buf)); - - if (chall_len > 16) { - warn("oversized Apple-Challenge!"); - free(chall); - return; - } - memcpy(bp, chall, chall_len); - free(chall); - bp += chall_len; - -#ifdef AF_INET6 - if (fdsa.SAFAMILY == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa); - memcpy(bp, sa6->sin6_addr.s6_addr, 16); - bp += 16; - } else -#endif - { - struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa); - memcpy(bp, &sa->sin_addr.s_addr, 4); - bp += 4; - } - - for (i = 0; i < 6; i++) - *bp++ = config.hw_addr[i]; - - int buflen, resplen; - buflen = bp - buf; - if (buflen < 0x20) - buflen = 0x20; - - uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); - char *encoded = base64_enc(challresp, resplen); - if (encoded == NULL) - die("could not allocate memory for \"encoded\""); - // strip the padding. - char *padding = strchr(encoded, '='); - if (padding) - *padding = 0; - - msg_add_header(resp, "Apple-Response", encoded); // will be freed when the response is freed. - free(challresp); - free(encoded); -} - -static char *make_nonce(void) { - uint8_t random[8]; - int fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) - die("could not open /dev/urandom!"); - // int ignore = - if (read(fd, random, sizeof(random)) != sizeof(random)) - debug(1, "Error reading /dev/urandom"); - close(fd); - return base64_enc(random, 8); // returns a pointer to malloc'ed memory -} - -static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { - - if (!config.password) - return 0; - if (!*nonce) { - *nonce = make_nonce(); - goto authenticate; - } - - char *hdr = msg_get_header(req, "Authorization"); - if (!hdr || strncmp(hdr, "Digest ", 7)) - goto authenticate; - - char *realm = strstr(hdr, "realm=\""); - char *username = strstr(hdr, "username=\""); - char *response = strstr(hdr, "response=\""); - char *uri = strstr(hdr, "uri=\""); - - if (!realm || !username || !response || !uri) - goto authenticate; - - char *quote; - realm = strchr(realm, '"') + 1; - if (!(quote = strchr(realm, '"'))) - goto authenticate; - *quote = 0; - username = strchr(username, '"') + 1; - if (!(quote = strchr(username, '"'))) - goto authenticate; - *quote = 0; - response = strchr(response, '"') + 1; - if (!(quote = strchr(response, '"'))) - goto authenticate; - *quote = 0; - uri = strchr(uri, '"') + 1; - if (!(quote = strchr(uri, '"'))) - goto authenticate; - *quote = 0; - - uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - -#ifdef CONFIG_OPENSSL - MD5_CTX ctx; - - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - MD5_Init(&ctx); - MD5_Update(&ctx, username, strlen(username)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, realm, strlen(realm)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, config.password, strlen(config.password)); - MD5_Final(digest_urp, &ctx); - MD5_Init(&ctx); - MD5_Update(&ctx, req->method, strlen(req->method)); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, uri, strlen(uri)); - MD5_Final(digest_mu, &ctx); - pthread_setcancelstate(oldState, NULL); -#endif - -#ifdef CONFIG_MBEDTLS -#if MBEDTLS_VERSION_MINOR >= 7 - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); - mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm)); - mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password)); - mbedtls_md5_finish_ret(&tctx, digest_urp); - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method)); - mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); - mbedtls_md5_finish_ret(&tctx, digest_mu); -#else - mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - mbedtls_md5_finish(&tctx, digest_urp); - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - mbedtls_md5_finish(&tctx, digest_mu); -#endif -#endif - -#ifdef CONFIG_POLARSSL - md5_context tctx; - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)username, strlen(username)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - md5_finish(&tctx, digest_urp); - md5_starts(&tctx); - md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - md5_finish(&tctx, digest_mu); -#endif - - int i; - unsigned char buf[33]; - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]); - -#ifdef CONFIG_OPENSSL - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - MD5_Init(&ctx); - MD5_Update(&ctx, buf, 32); - MD5_Update(&ctx, ":", 1); - MD5_Update(&ctx, *nonce, strlen(*nonce)); - MD5_Update(&ctx, ":", 1); - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - MD5_Update(&ctx, buf, 32); - MD5_Final(digest_total, &ctx); - pthread_setcancelstate(oldState, NULL); -#endif - -#ifdef CONFIG_MBEDTLS -#if MBEDTLS_VERSION_MINOR >= 7 - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, buf, 32); - mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - mbedtls_md5_update_ret(&tctx, buf, 32); - mbedtls_md5_finish_ret(&tctx, digest_total); -#else - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_finish(&tctx, digest_total); -#endif -#endif - -#ifdef CONFIG_POLARSSL - md5_starts(&tctx); - md5_update(&tctx, buf, 32); - md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - md5_update(&tctx, (unsigned char *)":", 1); - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - md5_update(&tctx, buf, 32); - md5_finish(&tctx, digest_total); -#endif - - for (i = 0; i < 16; i++) - snprintf((char *)buf + 2 * i, 3, "%02x", digest_total[i]); - - if (!strcmp(response, (const char *)buf)) - return 0; - warn("Password authorization failed."); - -authenticate: - resp->respcode = 401; - int hdrlen = strlen(*nonce) + 40; - char *authhdr = malloc(hdrlen); - snprintf(authhdr, hdrlen, "Digest realm=\"raop\", nonce=\"%s\"", *nonce); - msg_add_header(resp, "WWW-Authenticate", authhdr); - free(authhdr); - return 1; -} - -void rtsp_conversation_thread_cleanup_function(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - - debug(2, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - conn->connection_number); -#ifdef CONFIG_AIRPLAY_2 - // AP2 - teardown_phase_one(conn); - teardown_phase_two(conn); -#else - // AP1 - if (have_play_lock(conn)) { - teardown(conn); - release_play_lock(conn); - } -#endif - - debug(3, "Connection %d: terminating -- closing timing, control and audio sockets...", - conn->connection_number); - if (conn->control_socket) { - debug(3, "Connection %d: terminating -- closing control_socket %d.", - conn->connection_number, conn->control_socket); - close(conn->control_socket); - conn->control_socket = 0; - } - if (conn->timing_socket) { - debug(3, "Connection %d: terminating -- closing timing_socket %d.", - conn->connection_number, conn->timing_socket); - close(conn->timing_socket); - conn->timing_socket = 0; - } - if (conn->audio_socket) { - debug(3, "Connection %d: terminating -- closing audio_socket %d.", - conn->connection_number, conn->audio_socket); - close(conn->audio_socket); - conn->audio_socket = 0; - } - if (conn->fd > 0) { - debug(1, "Connection %d: terminating -- closing RTSP connection socket %d: from %s:%u to self at %s:%u.", - conn->connection_number, conn->fd, conn->client_ip_string, conn->client_rtsp_port, - conn->self_ip_string, conn->self_rtsp_port); - close(conn->fd); - conn->fd = 0; - } - if (conn->auth_nonce) { - free(conn->auth_nonce); - conn->auth_nonce = NULL; - } - -#ifdef CONFIG_AIRPLAY_2 - buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.plaintext_read_buffer, -1); - buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.encrypted_read_buffer, -1); - pair_cipher_free(conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx); - pair_setup_free(conn->ap2_pairing_context.setup_ctx); - pair_verify_free(conn->ap2_pairing_context.verify_ctx); - if (conn->airplay_gid) { - free(conn->airplay_gid); - conn->airplay_gid = NULL; - } -#endif - - rtp_terminate(conn); - - if (conn->dacp_id) { - free(conn->dacp_id); - conn->dacp_id = NULL; - } - - if (conn->UserAgent) { - free(conn->UserAgent); - conn->UserAgent = NULL; - } - - // remove flow control and mutexes - int rc = pthread_mutex_destroy(&conn->volume_control_mutex); - if (rc) - debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, - rc); - rc = pthread_cond_destroy(&conn->flowcontrol); - if (rc) - debug(1, "Connection %d: error %d destroying flow control condition variable.", - conn->connection_number, rc); - rc = pthread_mutex_destroy(&conn->ab_mutex); - if (rc) - debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc); - rc = pthread_mutex_destroy(&conn->flush_mutex); - if (rc) - debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); - - debug(3, "Cancel watchdog thread."); - pthread_cancel(conn->player_watchdog_thread); - debug(3, "Join watchdog thread."); - pthread_join(conn->player_watchdog_thread, NULL); - debug(3, "Delete watchdog mutex."); - pthread_mutex_destroy(&conn->watchdog_mutex); - - // debug(3, "Connection %d: Checking play lock.", conn->connection_number); - // release_play_lock(conn); - - debug(2, "Connection %d: Closed.", conn->connection_number); - conn->running = 0; - pthread_setcancelstate(oldState, NULL); -} - -void msg_cleanup_function(void *arg) { - // debug(3, "msg_cleanup_function called."); - msg_free((rtsp_message **)arg); -} - -static void *rtsp_conversation_thread_func(void *pconn) { - rtsp_conn_info *conn = pconn; - - // create the watchdog mutex, initialise the watchdog time and start the watchdog thread; - conn->watchdog_bark_time = get_absolute_time_in_ns(); - pthread_mutex_init(&conn->watchdog_mutex, NULL); - pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); - - int rc = pthread_mutex_init(&conn->flush_mutex, NULL); - if (rc) - die("Connection %d: error %d initialising flush_mutex.", conn->connection_number, rc); - rc = pthread_mutex_init(&conn->ab_mutex, NULL); - if (rc) - die("Connection %d: error %d initialising ab_mutex.", conn->connection_number, rc); - rc = pthread_cond_init(&conn->flowcontrol, NULL); - if (rc) - die("Connection %d: error %d initialising flow control condition variable.", - conn->connection_number, rc); - rc = pthread_mutex_init(&conn->volume_control_mutex, NULL); - if (rc) - die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc); - - // nothing before this is cancellable - pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); - - rtp_initialise(conn); - char *hdr = NULL; - - enum rtsp_read_request_response reply; - - int rtsp_read_request_attempt_count = 1; // 1 means exit immediately - rtsp_message *req, *resp; - -#ifdef CONFIG_AIRPLAY_2 - conn->ap2_audio_buffer_size = 1024 * 1024 * 8; -#endif - - while (conn->stop == 0) { - int debug_level = 3; // for printing the request and response - reply = rtsp_read_request(conn, &req); - if (reply == rtsp_read_request_response_ok) { - pthread_cleanup_push(msg_cleanup_function, (void *)&req); - resp = msg_init(); - pthread_cleanup_push(msg_cleanup_function, (void *)&resp); - resp->respcode = 501; // Not Implemented - int dl = debug_level; - if ((strcmp(req->method, "OPTIONS") == 0) || - (strcmp(req->method, "POST") == - 0)) // the options message is very common, so don't log it until level 3 - dl = 3; - - if (conn->airplay_stream_category == remote_control_stream) { - debug(dl, "Connection %d (RC): Received an RTSP Packet of type \"%s\":", - conn->connection_number, req->method), - debug_log_rtsp_message(dl, NULL, req); - } else { - debug(dl, "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number, - req->method), - debug_log_rtsp_message(dl, NULL, req); - } - apple_challenge(conn->fd, req, resp); - hdr = msg_get_header(req, "CSeq"); - if (hdr) - msg_add_header(resp, "CSeq", hdr); - // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); -#ifdef CONFIG_AIRPLAY_2 - msg_add_header(resp, "Server", "AirTunes/366.0"); -#else - msg_add_header(resp, "Server", "AirTunes/105.1"); -#endif - - if ((conn->authorized == 1) || (rtsp_auth(&conn->auth_nonce, req, resp)) == 0) { - conn->authorized = 1; // it must have been authorized or didn't need a password - struct method_handler *mh; - int method_selected = 0; - for (mh = method_handlers; mh->method; mh++) { - if (!strcmp(mh->method, req->method)) { - method_selected = 1; -#ifdef CONFIG_AIRPLAY_2 - if (conn->airplay_type == ap_1) - mh->ap1_handler(conn, req, resp); - else - mh->ap2_handler(conn, req, resp); -#else - mh->handler(conn, req, resp); -#endif - break; - } - } - if (method_selected == 0) { - debug(1, - "Connection %d: Unrecognised and unhandled rtsp request \"%s\". HTTP Response Code " - "501 (\"Not Implemented\") returned.", - conn->connection_number, req->method); - - int y = req->contentlength; - if (y > 0) { - char obf[4096]; - if (y > 4096) - y = 4096; - char *p = req->content; - char *obfp = obf; - int obfc; - for (obfc = 0; obfc < y; obfc++) { - snprintf(obfp, 3, "%02X", (unsigned int)*p); - p++; - obfp += 2; - }; - *obfp = 0; - debug(1, "Content: \"%s\".", obf); - } - } - } - if (conn->airplay_stream_category == remote_control_stream) { - debug(dl, "Connection %d (RC): RTSP Response:", conn->connection_number); - debug_log_rtsp_message(dl, NULL, resp); - } else { - debug(dl, "Connection %d: RTSP Response:", conn->connection_number); - debug_log_rtsp_message(dl, NULL, resp); - } - if (conn->stop == 0) { - int err = msg_write_response(conn, resp); - if (err) { - debug(1, - "Connection %d: Unable to write an RTSP message response. Terminating the " - "connection.", - conn->connection_number); - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the RTSP socket to abort due to a write error on closing."); - conn->stop = 1; - // if (debuglev >= 1) - // debuglev = 3; // see what happens next - } - } - pthread_cleanup_pop(1); - pthread_cleanup_pop(1); - } else { - int tstop = 0; - if (reply == rtsp_read_request_response_immediate_shutdown_requested) - tstop = 1; - else if ((reply == rtsp_read_request_response_channel_closed) || - (reply == rtsp_read_request_response_read_error)) { - if (conn->player_thread) { - rtsp_read_request_attempt_count--; - if (rtsp_read_request_attempt_count == 0) { - tstop = 1; - if (reply == rtsp_read_request_response_read_error) { - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - int err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the RTSP socket to abort due to a read error on closing."); - } - // debuglev = 3; // see what happens next - } else { - if (reply == rtsp_read_request_response_channel_closed) - debug(2, - "Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).", - conn->connection_number, rtsp_read_request_attempt_count); - if (reply == rtsp_read_request_response_read_error) - debug(2, "Connection %d: RTSP channel read error -- will try again %d time(s).", - conn->connection_number, rtsp_read_request_attempt_count); - usleep(20000); - } - } else { - tstop = 1; - } - } else if (reply == rtsp_read_request_response_bad_packet) { - char *response_text = "RTSP/1.0 400 Bad Request\r\nServer: AirTunes/105.1\r\n\r\n"; - ssize_t reply = write(conn->fd, response_text, strlen(response_text)); - if (reply == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, - (char *)errorstring); - } else if (reply != (ssize_t)strlen(response_text)) { - debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", - strlen(response_text), reply); - } - } else { - debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.", - conn->connection_number, (int)reply); - } - if (tstop) { - debug(3, "Connection %d: Terminate RTSP connection.", conn->connection_number); - conn->stop = 1; - } - } - } - pthread_cleanup_pop(1); - pthread_exit(NULL); - debug(1, "Connection %d: RTSP thread exit.", conn->connection_number); -} - -/* -// this function is not thread safe. -static const char *format_address(struct sockaddr *fsa) { - static char string[INETx_ADDRSTRLEN]; - void *addr; -#ifdef AF_INET6 - if (fsa->sa_family == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa); - addr = &(sa6->sin6_addr); - } else -#endif - { - struct sockaddr_in *sa = (struct sockaddr_in *)(fsa); - addr = &(sa->sin_addr); - } - return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); -} -*/ - -void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - debug(2, "rtsp_listen_loop_cleanup_handler called."); - cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners - int *sockfd = (int *)arg; - if (sockfd) { - int i; - for (i = 1; i <= sockfd[0]; i++) { - debug(2, "closing socket %d.", sockfd[i]); - close(sockfd[i]); - } - free(sockfd); - } - pthread_setcancelstate(oldState, NULL); -} - -void *rtsp_listen_loop(__attribute((unused)) void *arg) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - struct addrinfo hints, *info, *p; - char portstr[6]; - int *sockfd = NULL; - int nsock = 0; - int i, ret; - - playing_conn = NULL; // the data structure representing the connection that has the player. - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - - snprintf(portstr, 6, "%d", config.port); - - // debug(1,"listen socket port request is \"%s\".",portstr); - - ret = getaddrinfo(NULL, portstr, &hints, &info); - if (ret) { - die("getaddrinfo failed: %s", gai_strerror(ret)); - } - - for (p = info; p; p = p->ai_next) { - ret = 0; - int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); - int yes = 1; - - // Handle socket open failures if protocol unavailable (or IPV6 not handled) - if (fd != -1) { - // Set the RTSP socket to close on exec() of child processes - // otherwise background run_this_before_play_begins or run_this_after_play_ends commands - // that are sleeping prevent the daemon from being restarted because - // the listening RTSP port is still in use. - // See: https://github.com/mikebrady/shairport-sync/issues/329 - fcntl(fd, F_SETFD, FD_CLOEXEC); - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - struct timeval tv; - tv.tv_sec = 3; // three seconds write timeout - tv.tv_usec = 0; - if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - - if ((config.dont_check_timeout == 0) && (config.timeout != 0)) { - tv.tv_sec = config.timeout; // 120 seconds read timeout by default. - tv.tv_usec = 0; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) - debug(1, "Error %d setting read timeout for rtsp connection.", errno); - } -#ifdef IPV6_V6ONLY - // some systems don't support v4 access on v6 sockets, but some do. - // since we need to account for two sockets we might as well - // always. - if (p->ai_family == AF_INET6) { - ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); - } -#endif - - if (!ret) - ret = bind(fd, p->ai_addr, p->ai_addrlen); - - // one of the address families will fail on some systems that - // report its availability. do not complain. - - if (ret) { - char *family; -#ifdef AF_INET6 - if (p->ai_family == AF_INET6) { - family = "IPv6"; - } else -#endif - family = "IPv4"; - debug(1, "unable to listen on %s port %d. The error is: \"%s\".", family, config.port, - strerror(errno)); - } else { - listen(fd, 255); - nsock++; - sockfd = realloc(sockfd, (nsock + 1) * sizeof(int)); - sockfd[nsock] = fd; - sockfd[0] = nsock; // the first entry is the number of sockets in the array - } - } - - /* - listen(fd, 5); - nsock++; - sockfd = realloc(sockfd, nsock * sizeof(int)); - sockfd[nsock - 1] = fd; - */ - } - - freeaddrinfo(info); - - if (nsock) { - int maxfd = -1; - fd_set fds; - FD_ZERO(&fds); - // skip the first element in sockfd -- it's the count - for (i = 1; i <= nsock; i++) { - if (sockfd[i] > maxfd) - maxfd = sockfd[i]; - } - - char **t1 = txt_records; // ap1 test records - char **t2 = NULL; // possibly two text records -#ifdef CONFIG_AIRPLAY_2 - // make up a secondary set of text records - t2 = secondary_txt_records; // second set of text records in AirPlay 2 only -#endif - build_bonjour_strings(NULL); // no conn yet - mdns_register(t1, t2); // note that the dacp thread could still be using the mdns stuff after - // all player threads have been terminated, so mdns_unregister can't be - // in the rtsp_listen_loop cleanup. - - pthread_setcancelstate(oldState, NULL); - int acceptfd; - struct timeval tv; - pthread_cleanup_push(rtsp_listen_loop_cleanup_handler, (void *)sockfd); - do { - pthread_testcancel(); - tv.tv_sec = 60; - tv.tv_usec = 0; - - // skip the first element in sockfd -- it's the count - for (i = 1; i <= nsock; i++) - FD_SET(sockfd[i], &fds); - - ret = select(maxfd + 1, &fds, 0, 0, &tv); - if (ret < 0) { - if (errno == EINTR) - continue; - break; - } - - cleanup_threads(); - - acceptfd = -1; - // skip the first element in sockfd -- it's the count - for (i = 1; i <= nsock; i++) { - if (FD_ISSET(sockfd[i], &fds)) { - acceptfd = sockfd[i]; - break; - } - } - if (acceptfd < 0) // timeout - continue; - - rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); - if (conn == 0) - die("Couldn't allocate memory for an rtsp_conn_info record."); - memset(conn, 0, sizeof(rtsp_conn_info)); - conn->connection_number = RTSP_connection_index++; -#ifdef CONFIG_AIRPLAY_2 - conn->airplay_type = ap_2; // changed if an ANNOUNCE is received - conn->timing_type = ts_ptp; // changed if an ANNOUNCE is received -#endif - - socklen_t size_of_reply = sizeof(SOCKADDR); - conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &size_of_reply); - if (conn->fd < 0) { - debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number, - config.port); - perror("failed to accept connection"); - free(conn); - } else { - size_of_reply = sizeof(SOCKADDR); - if (getsockname(conn->fd, (struct sockaddr *)&conn->local, &size_of_reply) == 0) { - // initialise the connection info - void *client_addr = NULL, *self_addr = NULL; - conn->connection_ip_family = conn->local.SAFAMILY; - -#ifdef AF_INET6 - if (conn->connection_ip_family == AF_INET6) { - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->remote; - client_addr = &(sa6->sin6_addr); - conn->client_rtsp_port = ntohs(sa6->sin6_port); - - sa6 = (struct sockaddr_in6 *)&conn->local; - self_addr = &(sa6->sin6_addr); - conn->self_rtsp_port = ntohs(sa6->sin6_port); - conn->self_scope_id = sa6->sin6_scope_id; - } -#endif - if (conn->connection_ip_family == AF_INET) { - struct sockaddr_in *sa4 = (struct sockaddr_in *)&conn->remote; - client_addr = &(sa4->sin_addr); - conn->client_rtsp_port = ntohs(sa4->sin_port); - - sa4 = (struct sockaddr_in *)&conn->local; - self_addr = &(sa4->sin_addr); - conn->self_rtsp_port = ntohs(sa4->sin_port); - } - - inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string, - sizeof(conn->client_ip_string)); - inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, - sizeof(conn->self_ip_string)); - - debug(2, "Connection %d: New connection from %s:%u to self at %s:%u.", - conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - conn->self_ip_string, conn->self_rtsp_port); - conn->connection_start_time = get_absolute_time_in_ns(); - } else { - debug(1, "Error figuring out Shairport Sync's own IP number."); - } - - ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, - conn); // also acts as a memory barrier - if (ret) { - char errorstring[1024]; - strerror_r(ret, (char *)errorstring, sizeof(errorstring)); - die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", - conn->connection_number, ret, (char *)errorstring); - } - debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); - conn->running = 1; // this must happen before the thread is tracked - track_thread(conn); - } - } while (1); - pthread_cleanup_pop(1); // should never happen - } else { - warn("could not establish a service on port %d -- program terminating. Is another instance of " - "Shairport Sync running on this device?", - config.port); - } - debug(1, "Oops -- fell out of the RTSP select loop"); - pthread_exit(NULL); -} diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 47aa3537..97b7de3f 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -25,6 +25,8 @@ + +