From 986f9587de5be3c8273f3fbe6f89052edbf688e7 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:49:32 +0100 Subject: [PATCH] Rebuild the locking mechanisms for safe interruption of an existing play session. --- dbus-service.c | 18 +- player.c | 83 ++-- player.h | 7 +- rtsp.c | 1008 +++++++++++++++++++++++------------------------- rtsp.h | 2 +- 5 files changed, 548 insertions(+), 570 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index b4a9cc9e..a25d9962 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -635,13 +635,14 @@ gboolean notify_volume_callback(ShairportSync *skeleton, if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) { debug(2, ">> setting volume to %7.4f.", iv); - lock_player(); - if (playing_conn != NULL) { - player_volume(iv, playing_conn); - playing_conn->own_airplay_volume = iv; - playing_conn->own_airplay_volume_set = 1; + pthread_cleanup_debug_mutex_lock(&principal_conn_lock, 100000, 1); + + if (principal_conn != NULL) { + player_volume(iv, principal_conn); + principal_conn->own_airplay_volume = iv; + principal_conn->own_airplay_volume_set = 1; } - unlock_player(); + pthread_cleanup_pop(1); // release the principal_conn lock config.airplay_volume = iv; config.last_access_to_volume_info_time = get_absolute_time_in_ns(); } else { @@ -873,8 +874,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv static gboolean on_handle_drop_session(ShairportSync *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { - if (playing_conn != NULL) - debug(1, ">> stopping current play session"); get_play_lock(NULL, 1); // stop any current session and don't replace it shairport_sync_complete_drop_session(skeleton, invocation); return TRUE; @@ -1071,7 +1070,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name if (config.volume_control_profile == VCP_standard) shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "standard"); else if (config.volume_control_profile == VCP_dasl_tapered) - shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "dasl_tapered"); + shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), + "dasl_tapered"); else shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat"); diff --git a/player.c b/player.c index a5b0681b..3a2fb382 100644 --- a/player.c +++ b/player.c @@ -101,7 +101,7 @@ #include "activity_monitor.h" -// mstop) { - debug(3, "Connection %d: Stop the output backend.", conn->connection_number); - config.output->stop(); - } - } else { - debug(1, "This is not the playing conn."); + if ((principal_conn == conn) && (conn != NULL)) { + if (config.output->stop) { + debug(2, "Connection %d: Stop the output backend.", conn->connection_number); + config.output->stop(); } - pthread_cleanup_pop(1); // unlock the mutex } else { - debug(1, "Can not acquire play lock."); + if (conn != NULL) + debug(1, "Connection %d: this conn is not the principal_conn.", conn->connection_number); } int oldState; @@ -1900,8 +1895,6 @@ void player_thread_cleanup_handler(void *arg) { if (conn->stream.type == ast_apple_lossless) terminate_decoders(conn); - // reset_anchor_info(conn); - // release_play_lock(conn); conn->rtp_running = 0; pthread_setcancelstate(oldState, NULL); debug(2, "Connection %d: player terminated.", conn->connection_number); @@ -3631,14 +3624,21 @@ int player_prepare_to_play(rtsp_conn_info *conn) { } int player_play(rtsp_conn_info *conn) { - pthread_t *pt = malloc(sizeof(pthread_t)); - if (pt == NULL) - die("Couldn't allocate space for pthread_t"); - conn->player_thread = pt; - int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn); - if (rc) - debug(1, "Error creating player_thread: %s", strerror(errno)); - + debug(2, "Connection %d: player_play.", conn->connection_number); + pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1); + if (conn->player_thread == NULL) { + pthread_t *pt = malloc(sizeof(pthread_t)); + if (pt == NULL) + die("Couldn't allocate space for pthread_t"); + int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn); + if (rc) + debug(1, "Connection %d: error creating player_thread: %s", conn->connection_number, + strerror(errno)); + conn->player_thread = pt; // set _after_ creation of thread + } else { + debug(1, "Connection %d: player thread already exists.", conn->connection_number); + } + pthread_cleanup_pop(1); // release the player_create_delete_mutex #ifdef CONFIG_METADATA send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points #endif @@ -3647,35 +3647,38 @@ int player_play(rtsp_conn_info *conn) { int player_stop(rtsp_conn_info *conn) { // note -- this may be called from another connection thread. - // int dl = debuglev; - // debuglev = 3; - debug(3, "player_stop"); - if (conn->player_thread) { -#ifdef CONFIG_AIRPLAY_2 - ptp_send_control_message_string("E"); // signify play is "E"nding -#endif + debug(2, "Connection %d: player_stop.", conn->connection_number); + int response = 0; // okay + pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1); + pthread_t *pt = conn->player_thread; + if (pt) { debug(3, "player_thread cancel..."); - pthread_cancel(*conn->player_thread); + conn->player_thread = NULL; // cleared _before_ cancelling of thread + pthread_cancel(*pt); debug(3, "player_thread join..."); - if (pthread_join(*conn->player_thread, NULL) == -1) { + if (pthread_join(*pt, NULL) == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "Connection %d: error %d joining player thread: \"%s\".", conn->connection_number, errno, (char *)errorstring); } else { - debug(3, "player_thread joined."); + debug(2, "Connection %d: player_stop successful.", conn->connection_number); } - free(conn->player_thread); - conn->player_thread = NULL; + free(pt); + response = 0; // deleted + } else { + debug(2, "Connection %d: no player thread.", conn->connection_number); + response = -1; // already deleted or never created... + } + pthread_cleanup_pop(1); // release the player_create_delete_mutex + if (response == 0) { // if the thread was just stopped and deleted... +#ifdef CONFIG_AIRPLAY_2 + ptp_send_control_message_string("E"); // signify play is "E"nding +#endif #ifdef CONFIG_METADATA send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif - // debuglev = dl; command_stop(); - return 0; - } else { - debug(3, "Connection %d: player thread already deleted.", conn->connection_number); - // debuglev = dl; - return -1; } + return response; } diff --git a/player.h b/player.h index 55f77e2e..c5cfd002 100644 --- a/player.h +++ b/player.h @@ -107,7 +107,8 @@ typedef enum { unspecified_stream_category = 0, ptp_stream, ntp_stream, - remote_control_stream + remote_control_stream, + classic_airplay_stream } airplay_stream_c; // "c" for category #ifdef CONFIG_AIRPLAY_2 @@ -221,7 +222,7 @@ typedef struct { int32_t last_seqno_read; // mutexes and condition variables pthread_cond_t flowcontrol; - pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex; + pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex, player_create_delete_mutex; int fix_volume; double own_airplay_volume; @@ -414,7 +415,7 @@ typedef struct { uint64_t dac_buffer_queue_minimum_length; } rtsp_conn_info; -extern pthread_mutex_t playing_conn_lock; +extern pthread_mutex_t principal_conn_lock; extern int statistics_row; // will be reset to zero when debug level changes or statistics enabled void reset_buffer(rtsp_conn_info *conn); diff --git a/rtsp.c b/rtsp.c index a61ade1b..dfa1c668 100644 --- a/rtsp.c +++ b/rtsp.c @@ -93,7 +93,8 @@ #include "ptp-utilities.h" #ifdef HAVE_LIBPLIST_GE_2_3_0 -#define plist_from_memory(plist_data, length, plist) plist_from_memory((plist_data), (length), (plist), NULL) +#define plist_from_memory(plist_data, length, plist) \ + plist_from_memory((plist_data), (length), (plist), NULL) #endif #endif @@ -134,13 +135,18 @@ enum rtsp_read_request_response { rtsp_read_request_response_error }; -rtsp_conn_info *playing_conn; +static int nconns = 0; // i.e. the size if the conns array +rtsp_conn_info *principal_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 trying to make a conn the principal conn, +// e.g. during an ANNOUNCE (Classic AirPlay) or SETUP (AirPlay 2) +pthread_mutex_t principal_conn_acquisition_lock = PTHREAD_MUTEX_INITIALIZER; + +// always lock this when accessing the principal conn value +pthread_mutex_t principal_conn_lock = PTHREAD_MUTEX_INITIALIZER; // always lock this when accessing the list of connection threads pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER; @@ -511,131 +517,94 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { #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; +// 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(1, "Connection %d: cancelled.", conns[i]->connection_number); + } else if (conns[i] != NULL) { + debug(1, "Connection %d: not cancelled.", conns[i]->connection_number); + } } - return response; + 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(1, "Connection %d: joined.", conns[i]->connection_number); + free(conns[i]); + conns[i] = NULL; + } + } + debug_mutex_unlock(&conns_lock, 3); } +// The principal_conn variable points to the connection that +// controls the mDNS status and flags and that is potentially +// in control of the playing subsystem to output audio to a backend +// the principal_conn variable may be NULL -void release_hold_on_play_lock(__attribute__((unused)) rtsp_conn_info *conn) { - pthread_mutex_unlock(&playing_conn_lock); -} +// the principal_conn is set by an ANNOUNCE message (Classic AirPlay) or +// by the initial SETUP (of a connection, not of a play session) message (AirPlay 2) and cleared +// when a session is terminated (AirPlay 2) -*/ +// In AirPlay 2, only one PTP connection can be live at any time, and it is the principal_conn. +// This is because, in AirPlay 2, the principal_conn connection +// also has control of the mDNS interface, and thus determines the state of the player as seen by +// other devices. -// 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 + pthread_cleanup_debug_mutex_lock(&principal_conn_lock, 100000, + 1); // don't let the principal_conn be changed + if (principal_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 + debug(2, "Connection %d: principal_conn released.", conn->connection_number); + principal_conn = NULL; // let it go } - unlock_player(); + pthread_cleanup_pop(1); // release the principal_conn lock } -// make conn the playing_conn, and kill the current session if permitted +// stop the current principal_conn from playing if necessary and make conn the principal_conn. + int get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) { + int response = 0; + pthread_cleanup_debug_mutex_lock(&principal_conn_lock, 100000, 1); + if (principal_conn != NULL) + debug(2, "Connection %d: is requested to relinquish principal_conn.", + principal_conn->connection_number); 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."); + debug(2, "Connection %d: request to acquire principal_conn.", conn->connection_number); // 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 (principal_conn == NULL) { + principal_conn = conn; + } else if (principal_conn == conn) { 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; + warn("Connection %d: request to re-acquire principal_conn!", + principal_conn->connection_number); } 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; + player_stop(principal_conn); + debug(2, "Connection %d: termination requested.", principal_conn->connection_number); + pthread_cancel(principal_conn->thread); + usleep(2000000); // don't know why this delay is needed. + principal_conn = conn; // make the conn the new principal_conn + response = 1; // interrupted an existing session + } else { + response = -1; // can't get it... } + if (principal_conn != NULL) + debug(3, "Connection %d has acquired principal_conn.", principal_conn->connection_number); + pthread_cleanup_pop(1); // release the principal_conn lock return response; } @@ -690,8 +659,6 @@ void *player_watchdog_thread_code(void *arg) { 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 @@ -718,36 +685,6 @@ static void track_thread(rtsp_conn_info *conn) { 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); -} - int old_connection_count = -1; void cleanup_threads(void) { @@ -759,7 +696,7 @@ void cleanup_threads(void) { 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.", + debug(2, "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); @@ -1368,11 +1305,14 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes int msg_size = -1; while (msg_size < 0) { - if (conn->stop != 0) { - debug(3, "Connection %d: Shutdown requested by client.", conn->connection_number); - reply = rtsp_read_request_response_immediate_shutdown_requested; - goto shutdown; - } + + /* + if (conn->stop != 0) { + debug(3, "Connection %d: Shutdown requested by client.", conn->connection_number); + reply = rtsp_read_request_response_immediate_shutdown_requested; + goto shutdown; + } + */ nread = read_from_rtsp_connection(conn, buf + inbuf, buflen - inbuf); @@ -1475,11 +1415,14 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes } } - if (conn->stop != 0) { - debug(1, "RTSP shutdown requested."); - reply = rtsp_read_request_response_immediate_shutdown_requested; - goto shutdown; - } + /* + 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; @@ -1644,6 +1587,9 @@ char *get_category_string(airplay_stream_c cat) { case remote_control_stream: category = "Remote Control stream"; break; + case classic_airplay_stream: + category = "Classic AirPlay stream"; + break; default: category = "Unexpected stream code"; break; @@ -1661,11 +1607,12 @@ void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *r 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 != NULL) && (principal_conn == conn)) { + // 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.", + debug(2, "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); @@ -2037,7 +1984,8 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message pthread_cleanup_push(mutex_unlock, &conn->flush_mutex); conn->ap2_rate = rate; if ((rate & 1) != 0) { - ptp_send_control_message_string("B"); // signify clock dependability period is "B"eginning (or resuming) + ptp_send_control_message_string( + "B"); // signify clock dependability period is "B"eginning (or resuming) debug(2, "Connection %d: Start playing, with anchor clock %" PRIx64 ".", conn->connection_number, conn->networkTimeTimelineID); activity_monitor_signify_activity(1); @@ -2737,7 +2685,7 @@ void teardown_phase_two(rtsp_conn_info *conn) { } conn->groupContainsGroupLeader = 0; config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay - build_bonjour_strings(conn); + build_bonjour_strings(NULL); 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); @@ -2745,7 +2693,6 @@ void teardown_phase_two(rtsp_conn_info *conn) { free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } - release_play_lock(conn); clear_ptp_clock(); } } @@ -2753,6 +2700,8 @@ void teardown_phase_two(rtsp_conn_info *conn) { void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { + debug(2, "Connection %d: TEARDOWN %s.", conn->connection_number, + get_category_string(conn->airplay_stream_category)); debug_log_rtsp_message(2, "TEARDOWN: ", req); // if (have_player(conn)) { resp->respcode = 200; @@ -2764,26 +2713,28 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag plist_t streams = plist_dict_get_item(messagePlist, "streams"); if (streams) { - debug(2, "Connection %d: TEARDOWN %s Close the stream.", conn->connection_number, + 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, + 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, + 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)); + 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; + release_play_lock(conn); + plist_free(messagePlist); resp->respcode = 200; } else { @@ -2808,22 +2759,22 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message 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, + // 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; - } + 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); } @@ -2846,7 +2797,8 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { } } debug(2, "RTSP Flush Requested: %u.", rtptime); - if (have_play_lock(conn)) { + + if ((conn != NULL) && (conn == principal_conn)) { #ifdef CONFIG_METADATA if (p) send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); @@ -2884,7 +2836,7 @@ static void check_and_send_plist_metadata(plist_t messagePlist, const char *plis void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int err; debug(2, "Connection %d: SETUP (AirPlay 2)", conn->connection_number); - // debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req); + debug_log_rtsp_message(2, "SETUP (AirPlay 2) SETUP incoming message", req); plist_t messagePlist = plist_from_rtsp_content(req); plist_t setupResponsePlist = plist_new_dict(); @@ -2921,15 +2873,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) 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, @@ -2949,7 +2892,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean); if (isRemoteControlOnlyBoolean != 0) { debug( - 1, + 2, "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); @@ -2972,178 +2915,206 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) // 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"); - } + // airplay 2 always allows interruption, so should never return -1 + if (get_play_lock(conn, 1) != -1) { - 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); - } +#ifdef CONFIG_METADATA + send_ssnc_metadata('conn', conn->client_ip_string, strlen(conn->client_ip_string), + 1); // before disconnecting an existing play +#endif + +#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 + + 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, "SETUP on Connection %d: No timingPeerInfo addresses in the array.", - conn->connection_number); + debug(1, "Invalid groupUUID"); } } else { - debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses", - conn->connection_number); + debug(1, "No groupUUID in SETUP"); } - // 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)); + // 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 { - 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)); + 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); + ptp_send_control_message_string( + "B"); // signify clock dependability period is "B"eginning (or continuing) + 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); } - } - 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); - ptp_send_control_message_string("B"); // signify clock dependability period is "B"eginning (or continuing) - 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); - } - listen(conn->event_socket, 128); // ensure socket is open before telling client + listen(conn->event_socket, 128); // ensure socket is open before telling client - debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number, - conn->local_event_port); + 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"); + 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); + 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 + plist_dict_set_item(setupResponsePlist, "eventPort", + plist_new_uint(conn->local_event_port)); + plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy - 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); - } + /* + 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'); + 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 { + // this should never happen! + debug(1, "SETUP on Connection %d: could not become principal conn.", + conn->connection_number); + resp->respcode = 453; + } } 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 @@ -3160,7 +3131,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) listen(conn->event_socket, 128); // ensure socket is open before telling client - debug(2, "Connection %d SETUP (RC): TCP Remote Control event port opened: %u.", + debug(1, "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, @@ -3179,6 +3150,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) 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); @@ -3202,7 +3174,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->connection_number, get_category_string(conn->airplay_stream_category)); if (conn->airplay_stream_category == ptp_stream) { // get stream[0] - ptp_send_control_message_string("B"); // signify clock dependability period is "B"eginning (or continuing) + ptp_send_control_message_string( + "B"); // signify clock dependability period is "B"eginning (or continuing) plist_t stream0 = plist_array_get_item(streams, 0); plist_t streams_array = plist_new_array(); // to hold the ports and stuff @@ -3280,7 +3253,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) 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 @@ -3332,9 +3304,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->rtp_running = 1; // hack! } break; case 103: { - debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); + debug(2, "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 @@ -3446,9 +3417,10 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) #endif void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: SETUP", conn->connection_number); + debug(2, "Connection %d: SETUP", conn->connection_number); resp->respcode = 451; // invalid arguments -- expect them - if (have_play_lock(conn)) { + // check this connection has the principal_conn, obtained during a prior ANNOUNCE + if ((conn != NULL) && (principal_conn == conn)) { uint16_t cport, tport; char *ar = msg_get_header(req, "Active-Remote"); if (ar) { @@ -3554,20 +3526,19 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { } 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); } + 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); + } } /* @@ -3613,15 +3584,16 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, shairport_sync_set_volume(shairportSyncSkeleton, volume); } else { #endif - lock_player(); - if (playing_conn == conn) { + pthread_cleanup_debug_mutex_lock(&principal_conn_lock, 100000, + 1); // don't let the principal_conn be changed + if (principal_conn == conn) { player_volume(volume, conn); } if (conn != NULL) { conn->own_airplay_volume = volume; conn->own_airplay_volume_set = 1; } - unlock_player(); + pthread_cleanup_pop(1); // release the principal_conn lock config.last_access_to_volume_info_time = get_absolute_time_in_ns(); #ifdef CONFIG_DBUS_INTERFACE } @@ -4484,10 +4456,13 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m } static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: ANNOUNCE", conn->connection_number); + debug(2, "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); + debug(2, "Connection %d: ANNOUNCE has acquired play lock.", conn->connection_number); + + conn->airplay_stream_category = classic_airplay_stream; // 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 @@ -4512,13 +4487,13 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag #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.", + debug(2, "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 + resp->respcode = 200; // presumed OK char *pssid = NULL; char *paesiv = NULL; char *prsaaeskey = NULL; @@ -4613,28 +4588,29 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag // debug(1,"Encrypted session requested"); } else { warn("Invalid Announce message -- missing paesiv or prsaaeskey."); - goto out; + resp->respcode = 456; // 456 - Header Field Not Valid for Resource + // goto out; } if (conn->stream.encrypted) { int len, keylen; uint8_t *aesiv = base64_dec(paesiv, &len); - if (len != 16) { + if (len == 16) { + memcpy(conn->stream.aesiv, aesiv, 16); + } else { + resp->respcode = 456; // 456 - Header Field Not Valid for Resource 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) { + if (keylen == 16) { + memcpy(conn->stream.aeskey, aeskey, 16); + } else { + resp->respcode = 456; // 456 - Header Field Not Valid for Resource warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); - free(aeskey); - goto out; } - memcpy(conn->stream.aeskey, aeskey, 16); free(aeskey); } @@ -4675,7 +4651,37 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); } - if (conn->stream.type == ast_unknown) { + if ((resp->respcode == 200) && (conn->stream.type != ast_unknown)) { + 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 + } + } else { 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 @@ -4688,50 +4694,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag 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; + debug(2, "Connection %d: ANNOUNCE has completed.", conn->connection_number); } else { + // can't get the principal_conn 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); } } @@ -5042,110 +5009,113 @@ authenticate: void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + if (conn != NULL) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); - debug(2, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.", - conn->connection_number); + debug(2, "Connection %d: %s rtsp_conversation_thread_func_cleanup_function called.", + conn->connection_number, get_category_string(conn->airplay_stream_category)); #ifdef CONFIG_AIRPLAY_2 - // AP2 - teardown_phase_one(conn); - teardown_phase_two(conn); + // AP2 + teardown_phase_one(conn); + teardown_phase_two(conn); #else - // AP1 - if (have_play_lock(conn)) { + // AP1 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(2, + 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( + 2, "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; - } + 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; - } + 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); + rtp_terminate(conn); - if (conn->dacp_id) { - free(conn->dacp_id); - conn->dacp_id = NULL; - } + if (conn->dacp_id) { + free(conn->dacp_id); + conn->dacp_id = NULL; + } - if (conn->UserAgent) { - free(conn->UserAgent); - conn->UserAgent = 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); + // remove flow control and mutexes - 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); + int rc = pthread_mutex_destroy(&conn->player_create_delete_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying player_create_delete_mutex.", + conn->connection_number, rc); + 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, "Connection %d: Checking play lock.", conn->connection_number); - // release_play_lock(conn); + 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(2, "Connection %d: Closed.", conn->connection_number); - conn->running = 0; - pthread_setcancelstate(oldState, NULL); + debug(2, "Connection %d: Closed.", conn->connection_number); + conn->running = 0; + pthread_setcancelstate(oldState, NULL); + } } void msg_cleanup_function(void *arg) { @@ -5175,6 +5145,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (rc) die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc); + rc = pthread_mutex_init(&conn->player_create_delete_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising player_create_delete_mutex.", conn->connection_number, + rc); + // nothing before this is cancellable pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); @@ -5199,7 +5174,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { pthread_cleanup_push(msg_cleanup_function, (void *)&resp); resp->respcode = 501; // Not Implemented int dl = debug_level; - //if ((strcmp(req->method, "OPTIONS") == 0) || + // 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; @@ -5273,24 +5248,23 @@ static void *rtsp_conversation_thread_func(void *pconn) { 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 - } + // 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; + pthread_cancel(conn->thread); } + // } pthread_cleanup_pop(1); pthread_cleanup_pop(1); } else { @@ -5348,8 +5322,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { } } pthread_cleanup_pop(1); + debug(2, "Connection %d: RTSP thread exit.", conn->connection_number); pthread_exit(NULL); - debug(1, "Connection %d: RTSP thread exit.", conn->connection_number); } /* @@ -5397,7 +5371,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) { int nsock = 0; int i, ret; - playing_conn = NULL; // the data structure representing the connection that has the player. + principal_conn = NULL; // the data structure representing the connection that has the player. memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; diff --git a/rtsp.h b/rtsp.h index 0725f664..59b3ae3d 100644 --- a/rtsp.h +++ b/rtsp.h @@ -3,7 +3,7 @@ #include "player.h" -extern rtsp_conn_info *playing_conn; +extern rtsp_conn_info *principal_conn; extern rtsp_conn_info **conns; void *rtsp_listen_loop(__attribute((unused)) void *arg); -- 2.47.3