]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Fix compilation bug when metadata isn't included in the build.
authorMike Brady <4265913+mikebrady@users.noreply.github.com>
Sat, 12 Nov 2022 16:57:12 +0000 (16:57 +0000)
committerMike Brady <4265913+mikebrady@users.noreply.github.com>
Sat, 12 Nov 2022 16:57:12 +0000 (16:57 +0000)
new-rtsp.c [new file with mode: 0644]
player.c

diff --git a/new-rtsp.c b/new-rtsp.c
new file mode 100644 (file)
index 0000000..8d11fbc
--- /dev/null
@@ -0,0 +1,5564 @@
+/*
+ * 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 <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <memory.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+
+#include "activity_monitor.h"
+#include "config.h"
+
+#ifdef CONFIG_OPENSSL
+#include <openssl/md5.h>
+#endif
+
+#ifdef CONFIG_MBEDTLS
+#include <mbedtls/md5.h>
+#include <mbedtls/version.h>
+#endif
+
+#ifdef CONFIG_POLARSSL
+#include <polarssl/md5.h>
+#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; obfc<message->contentlength; 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");
+}
+
+/*
+        <key>Identifier</key>
+        <string>21cc689d-d5de-4814-872c-71d1426b57e0</string>
+        <key>Enable_HK_Access_Control</key>
+        <true/>
+        <key>PublicKey</key>
+        <data>
+        qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
+        </data>
+        <key>Device_Name</key>
+        <string>MyDevice</string>
+        <key>Access_Control_Level</key>
+        <integer>0</integer>
+*/
+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("<unknown>");
+  }
+
+  // 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, "<item><type>%x</type><code>%x</code><length>%u</length>", 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<data encoding=\"base64\">\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, "</data>");
+    // 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, "</item>\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);
+}
index c6432eecfd9de2da43a081137522b160d7ed7a43..ec282c1c9de615d719784110fe074c7db375ed45 100644 (file)
--- a/player.c
+++ b/player.c
@@ -2741,24 +2741,32 @@ void *player_thread_func(void *arg) {
 #ifdef CONFIG_AIRPLAY_2
               if (conn->airplay_stream_type == realtime_stream) {
                 if (conn->airplay_type == ap_1) {
+#ifdef CONFIG_METADATA_HUB                
                   send_ssnc_metadata('styp', "Classic", strlen("Classic"), 1);
+#endif
                   if (config.statistics_requested)
                     inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 1 Compatible.",
                          conn->connection_number, inframe->given_timestamp);
                 } else {
+#ifdef CONFIG_METADATA_HUB                
                   send_ssnc_metadata('styp', "Realtime", strlen("Realtime"), 1);
+#endif
                   if (config.statistics_requested)
                     inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 2 Realtime.",
                          conn->connection_number, inframe->given_timestamp);
                 }
               } else {
+#ifdef CONFIG_METADATA_HUB                
                 send_ssnc_metadata('styp', "Buffered", strlen("Buffered"), 1);
+#endif
                 if (config.statistics_requested)
                   inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 2 Buffered.",
                        conn->connection_number, inframe->given_timestamp);
               }
 #else
+#ifdef CONFIG_METADATA_HUB                
               send_ssnc_metadata('styp', "Classic", strlen("Classic"), 1);
+#endif
               if (config.statistics_requested)
                 inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 1.", conn->connection_number, inframe->given_timestamp);
 #endif