]> git.ipfire.org Git - thirdparty/shairport-sync.git/blobdiff - common.c
Update check_classic_mac_basic.yml
[thirdparty/shairport-sync.git] / common.c
index f99c7752219b5c14fe5e33bc5c349851dae480ca..4dc75bc7f07df6eda1a209348cf4174b7ac81895 100644 (file)
--- a/common.c
+++ b/common.c
@@ -2,7 +2,7 @@
  * Utility routines. This file is part of Shairport.
  * Copyright (c) James Laird 2013
  * The volume to attenuation function vol2attn copyright (c) Mike Brady 2014
- * Further changes and additions (c) Mike Brady 2014 -- 2019
+ * Further changes and additions (c) Mike Brady 2014 -- 2021
  * All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person
  */
 
 #include "common.h"
+
+#ifdef CONFIG_USE_GIT_VERSION_STRING
+#include "gitversion.h"
+#endif
+
 #include <assert.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h> // PRIdPTR
+#include <libgen.h>
+#include <math.h>
 #include <memory.h>
 #include <poll.h>
 #include <popt.h>
 #include <time.h>
 #include <unistd.h>
 
+#include <ifaddrs.h>
+
+#ifdef COMPILE_FOR_LINUX
+#include <netpacket/packet.h>
+#endif
+
+#ifdef COMPILE_FOR_BSD
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netinet/in.h>
+#endif
+
 #ifdef COMPILE_FOR_OSX
 #include <CoreServices/CoreServices.h>
 #include <mach/mach.h>
 #include <mach/mach_time.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netinet/in.h>
 #endif
 
 #ifdef CONFIG_OPENSSL
-#include <openssl/bio.h>
-#include <openssl/buffer.h>
-#include <openssl/evp.h>
-#include <openssl/pem.h>
-#include <openssl/rsa.h>
+#include <openssl/aes.h> // needed for older AES stuff
+#include <openssl/bio.h> // needed for BIO_new_mem_buf
+#include <openssl/err.h> // needed for ERR_error_string, ERR_get_error
+#include <openssl/evp.h> // needed for EVP_PKEY_CTX_new, EVP_PKEY_sign_init, EVP_PKEY_sign
+#include <openssl/pem.h> // needed for PEM_read_bio_RSAPrivateKey, EVP_PKEY_CTX_set_rsa_padding
+#include <openssl/rsa.h> // needed for EVP_PKEY_CTX_set_rsa_padding
 #endif
 
 #ifdef CONFIG_POLARSSL
 void set_alsa_out_dev(char *);
 #endif
 
+#ifdef CONFIG_AIRPLAY_2
+#include "nqptp-shm-structures.h"
+#endif
+
 config_t config_file_stuff;
-pthread_t main_thread_id;
+int type_of_exit_cleanup;
 uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
 
 // always lock use this when accessing the ns_time_at_last_debug_message
@@ -120,8 +149,8 @@ static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
 static void (*sps_log)(int prio, const char *t, ...) = syslog;
 #endif
 
-void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
-  char s[1024];
+void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
+  char s[16384];
   va_list args;
   va_start(args, t);
   vsnprintf(s, sizeof(s), t, args);
@@ -129,25 +158,94 @@ void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
   fprintf(stderr, "%s\n", s);
 }
 
-void log_to_stderr() { sps_log = do_sps_log; }
-
-shairport_cfg config;
+void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
+  char s[16384];
+  va_list args;
+  va_start(args, t);
+  vsnprintf(s, sizeof(s), t, args);
+  va_end(args);
+  fprintf(stdout, "%s\n", s);
+}
 
-// accessors for multi-thread-access fields in the conn structure
+int create_log_file(const char *path) {
+  int fd = -1;
+  if (path != NULL) {
+    char *dirc = strdup(path);
+    if (dirc) {
+      char *dname = dirname(dirc);
+      // create the directory, if necessary
+      int result = 0;
+      if (dname) {
+        char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
+        if (pdir == NULL) {
+          mode_t oldumask = umask(000);
+          result = mkpath(dname, 0777);
+          umask(oldumask);
+        } else {
+          free(pdir);
+        }
+        if ((result == 0) || (result == -EEXIST)) {
+          // now open the file
+          fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL,
+                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+          if ((fd == -1) && (errno == EEXIST))
+            fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
+
+          if (fd >= 0) {
+            // now we switch to blocking mode
+            int flags = fcntl(fd, F_GETFL);
+            if (flags == -1) {
+              //                                                       strerror_r(errno, (char
+              //*)errorstring, sizeof(errorstring));
+              // debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".",
+              // errno,
+              // (char *)errorstring, pathname);
+            } else {
+              flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+              //                                                       if (flags == -1) {
+              //                                                               strerror_r(errno,
+              //(char *)errorstring, sizeof(errorstring));
+              // debug(1, "create_log_file -- error %d
+              //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+              //(char *)errorstring, pathname);
+            }
+          }
+        }
+      }
+      free(dirc);
+    }
+  }
+  return fd;
+}
 
-double get_config_airplay_volume() {
-  config_lock;
-  double v = config.airplay_volume;
-  config_unlock;
-  return v;
+void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
+  char s[16384];
+  va_list args;
+  va_start(args, t);
+  vsnprintf(s, sizeof(s), t, args);
+  va_end(args);
+  if (config.log_fd == -1)
+    config.log_fd = create_log_file(config.log_file_path);
+  if (config.log_fd >= 0) {
+    dprintf(config.log_fd, "%s\n", s);
+  } else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up
+    fprintf(stderr, "%s\n", s);
+  }
 }
 
-void set_config_airplay_volume(double v) {
-  config_lock;
-  config.airplay_volume = v;
-  config_unlock;
+void log_to_stderr() { sps_log = do_sps_log_to_stderr; }
+void log_to_stdout() { sps_log = do_sps_log_to_stdout; }
+void log_to_file() { sps_log = do_sps_log_to_fd; }
+void log_to_syslog() {
+#ifdef CONFIG_LIBDAEMON
+  sps_log = daemon_log;
+#else
+  sps_log = syslog;
+#endif
 }
 
+shairport_cfg config;
+
 volatile int debuglev = 0;
 
 sigset_t pselect_sigset;
@@ -179,41 +277,179 @@ uint16_t nextFreeUDPPort() {
   return UDPPortIndex;
 }
 
+// if port is zero, pick any port
+// otherwise, try the given port only
+int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, uint32_t scope_id,
+                         uint16_t *port, int *sock) {
+  int ret = 0; // no error
+  int local_socket = socket(ip_family, type, 0);
+  if (local_socket == -1)
+    ret = errno;
+  if (ret == 0) {
+    SOCKADDR myaddr;
+    memset(&myaddr, 0, sizeof(myaddr));
+    if (ip_family == AF_INET) {
+      struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
+      sa->sin_family = AF_INET;
+      sa->sin_port = ntohs(*port);
+      inet_pton(AF_INET, self_ip_address, &(sa->sin_addr));
+      ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in));
+    }
+#ifdef AF_INET6
+    if (ip_family == AF_INET6) {
+      struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr;
+      sa6->sin6_family = AF_INET6;
+      sa6->sin6_port = ntohs(*port);
+      inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr));
+      sa6->sin6_scope_id = scope_id;
+      ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6));
+    }
+#endif
+    if (ret < 0) {
+      ret = errno;
+      close(local_socket);
+      char errorstring[1024];
+      strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+      warn("error %d: \"%s\". Could not bind a port!", errno, errorstring);
+    } else {
+      uint16_t sport;
+      SOCKADDR local;
+      socklen_t local_len = sizeof(local);
+      ret = getsockname(local_socket, (struct sockaddr *)&local, &local_len);
+      if (ret < 0) {
+        ret = errno;
+        close(local_socket);
+        char errorstring[1024];
+        strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+        warn("error %d: \"%s\". Could not retrieve socket's port!", errno, errorstring);
+      } else {
+#ifdef AF_INET6
+        if (local.SAFAMILY == AF_INET6) {
+          struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&local;
+          sport = ntohs(sa6->sin6_port);
+        } else
+#endif
+        {
+          struct sockaddr_in *sa = (struct sockaddr_in *)&local;
+          sport = ntohs(sa->sin_port);
+        }
+        *sock = local_socket;
+        *port = sport;
+      }
+    }
+  }
+  return ret;
+}
+
+uint16_t bind_UDP_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) {
+  // look for a port in the range, if any was specified.
+  int ret = 0;
+
+  int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
+  if (local_socket == -1)
+    die("Could not allocate a socket.");
+
+  /*
+    int val = 1;
+    ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+    if (ret < 0) {
+      char errorstring[1024];
+      strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+      debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR");
+    }
+  */
+
+  SOCKADDR myaddr;
+  int tryCount = 0;
+  uint16_t desired_port;
+  do {
+    tryCount++;
+    desired_port = nextFreeUDPPort();
+    memset(&myaddr, 0, sizeof(myaddr));
+    if (ip_family == AF_INET) {
+      struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
+      sa->sin_family = AF_INET;
+      sa->sin_port = ntohs(desired_port);
+      inet_pton(AF_INET, self_ip_address, &(sa->sin_addr));
+      ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in));
+    }
+#ifdef AF_INET6
+    if (ip_family == AF_INET6) {
+      struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr;
+      sa6->sin6_family = AF_INET6;
+      sa6->sin6_port = ntohs(desired_port);
+      inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr));
+      sa6->sin6_scope_id = scope_id;
+      ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6));
+    }
+#endif
+
+  } while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
+           (tryCount < config.udp_port_range));
+
+  // debug(1,"UDP port chosen: %d.",desired_port);
+
+  if (ret < 0) {
+    close(local_socket);
+    char errorstring[1024];
+    strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+    die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- "
+        "it must be "
+        "at least 3, and 10 or more is suggested -- or "
+        "check for restrictive firewall settings or a bad router! UDP base is %u, range is %u and "
+        "current suggestion is %u.",
+        errno, errorstring, config.udp_port_base, config.udp_port_range, desired_port);
+  }
+
+  uint16_t sport;
+  SOCKADDR local;
+  socklen_t local_len = sizeof(local);
+  getsockname(local_socket, (struct sockaddr *)&local, &local_len);
+#ifdef AF_INET6
+  if (local.SAFAMILY == AF_INET6) {
+    struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&local;
+    sport = ntohs(sa6->sin6_port);
+  } else
+#endif
+  {
+    struct sockaddr_in *sa = (struct sockaddr_in *)&local;
+    sport = ntohs(sa->sin_port);
+  }
+  *sock = local_socket;
+  return sport;
+}
+
 int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
 
 void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
 
 char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
                                   const char *filename, const int linenumber, const char *prefix) {
-  size_t space_remaining = buffer_length;
   char *insertion_point = buffer;
   if (config.debugger_show_elapsed_time) {
-    snprintf(insertion_point, space_remaining, "% 20.9f", tss);
+    snprintf(insertion_point, buffer_length, "% 20.9f", tss);
     insertion_point = insertion_point + strlen(insertion_point);
-    space_remaining = space_remaining - strlen(insertion_point);
   }
   if (config.debugger_show_relative_time) {
-    snprintf(insertion_point, space_remaining, "% 20.9f", tsl);
+    snprintf(insertion_point, buffer_length, "% 20.9f", tsl);
     insertion_point = insertion_point + strlen(insertion_point);
-    space_remaining = space_remaining - strlen(insertion_point);
   }
   if (config.debugger_show_file_and_line) {
-    snprintf(insertion_point, space_remaining, " \"%s:%d\"", filename, linenumber);
+    snprintf(insertion_point, buffer_length, " \"%s:%d\"", filename, linenumber);
     insertion_point = insertion_point + strlen(insertion_point);
-    space_remaining = space_remaining - strlen(insertion_point);
   }
-
   if (prefix) {
-    snprintf(insertion_point, space_remaining, "%s", prefix);
+    snprintf(insertion_point, buffer_length, "%s", prefix);
     insertion_point = insertion_point + strlen(insertion_point);
   }
   return insertion_point;
 }
 
-void _die(const char *filename, const int linenumber, const char *format, ...) {
+void _die(const char *thefilename, const int linenumber, const char *format, ...) {
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  char b[1024];
+
+  char b[16384];
   b[0] = 0;
   char *s;
   if (debuglev) {
@@ -223,11 +459,15 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
     uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
     ns_time_at_last_debug_message = time_now;
     pthread_mutex_unlock(&debug_timing_lock);
+    char *basec = strdup(thefilename);
+    char *filename = basename(basec);
     s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                     1.0 * time_since_last_debug_message / 1000000000, filename,
                                     linenumber, " *fatal error: ");
+    free(basec);
   } else {
-    s = b;
+    strncpy(b, "fatal error: ", sizeof(b));
+    s = b + strlen(b);
   }
   va_list args;
   va_start(args, format);
@@ -235,13 +475,14 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
   va_end(args);
   sps_log(LOG_ERR, "%s", b);
   pthread_setcancelstate(oldState, NULL);
-  abort(); // exit() doesn't always work, by heaven.
+  type_of_exit_cleanup = TOE_emergency;
+  exit(EXIT_FAILURE);
 }
 
-void _warn(const char *filename, const int linenumber, const char *format, ...) {
+void _warn(const char *thefilename, const int linenumber, const char *format, ...) {
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  char b[1024];
+  char b[16384];
   b[0] = 0;
   char *s;
   if (debuglev) {
@@ -251,11 +492,15 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
     uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
     ns_time_at_last_debug_message = time_now;
     pthread_mutex_unlock(&debug_timing_lock);
+    char *basec = strdup(thefilename);
+    char *filename = basename(basec);
     s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                     1.0 * time_since_last_debug_message / 1000000000, filename,
                                     linenumber, " *warning: ");
+    free(basec);
   } else {
-    s = b;
+    strncpy(b, "warning: ", sizeof(b));
+    s = b + strlen(b);
   }
   va_list args;
   va_start(args, format);
@@ -265,12 +510,13 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
   pthread_setcancelstate(oldState, NULL);
 }
 
-void _debug(const char *filename, const int linenumber, int level, const char *format, ...) {
+void _debug(const char *thefilename, const int linenumber, int level, const char *format, ...) {
   if (level > debuglev)
     return;
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  char b[1024];
+
+  char b[16384];
   b[0] = 0;
   pthread_mutex_lock(&debug_timing_lock);
   uint64_t time_now = get_absolute_time_in_ns();
@@ -278,21 +524,24 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
   uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
   ns_time_at_last_debug_message = time_now;
   pthread_mutex_unlock(&debug_timing_lock);
+  char *basec = strdup(thefilename);
+  char *filename = basename(basec);
   char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                         1.0 * time_since_last_debug_message / 1000000000, filename,
                                         linenumber, " ");
+  free(basec);
   va_list args;
   va_start(args, format);
   vsnprintf(s, sizeof(b) - (s - b), format, args);
   va_end(args);
-  sps_log(LOG_DEBUG, "%s", b);
+  sps_log(LOG_INFO, b); // LOG_DEBUG is hard to read on macOS terminal
   pthread_setcancelstate(oldState, NULL);
 }
 
-void _inform(const char *filename, const int linenumber, const char *format, ...) {
+void _inform(const char *thefilename, const int linenumber, const char *format, ...) {
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  char b[1024];
+  char b[16384];
   b[0] = 0;
   char *s;
   if (debuglev) {
@@ -302,9 +551,12 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
     uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
     ns_time_at_last_debug_message = time_now;
     pthread_mutex_unlock(&debug_timing_lock);
+    char *basec = strdup(thefilename);
+    char *filename = basename(basec);
     s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                     1.0 * time_since_last_debug_message / 1000000000, filename,
                                     linenumber, " ");
+    free(basec);
   } else {
     s = b;
   }
@@ -558,25 +810,82 @@ static char super_secret_key[] =
 uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  RSA *rsa = NULL;
-  if (!rsa) {
-    BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
-    rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
-    BIO_free(bmem);
-  }
-
-  uint8_t *out = malloc(RSA_size(rsa));
-  switch (mode) {
-  case RSA_MODE_AUTH:
-    *outlen = RSA_private_encrypt(inlen, input, out, rsa, RSA_PKCS1_PADDING);
-    break;
-  case RSA_MODE_KEY:
-    *outlen = RSA_private_decrypt(inlen, input, out, rsa, RSA_PKCS1_OAEP_PADDING);
-    break;
-  default:
-    die("bad rsa mode");
+  uint8_t *out = NULL;
+  BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);                  // 1.0.2
+  EVP_PKEY *rsaKey = PEM_read_bio_PrivateKey(bmem, NULL, NULL, NULL); // 1.0.2
+  BIO_free(bmem);
+  size_t ol = 0;
+  if (rsaKey != NULL) {
+    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(rsaKey, NULL); // 1.0.2
+    if (ctx != NULL) {
+
+      switch (mode) {
+      case RSA_MODE_AUTH: {
+        if (EVP_PKEY_sign_init(ctx) > 0) {                                                // 1.0.2
+          if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0) {                 // 1.0.2
+            if (EVP_PKEY_sign(ctx, NULL, &ol, (const unsigned char *)input, inlen) > 0) { // 1.0.2
+              out = (unsigned char *)malloc(ol);
+              if (EVP_PKEY_sign(ctx, out, &ol, (const unsigned char *)input, inlen) > 0) { // 1.0.2
+                debug(3, "success with output length of %lu.", ol);
+              } else {
+                debug(1, "error 2 \"%s\" with EVP_PKEY_sign:",
+                      ERR_error_string(ERR_get_error(), NULL));
+              }
+            } else {
+              debug(1,
+                    "error 1 \"%s\" with EVP_PKEY_sign:", ERR_error_string(ERR_get_error(), NULL));
+            }
+          } else {
+            debug(1, "error \"%s\" with EVP_PKEY_CTX_set_rsa_padding:",
+                  ERR_error_string(ERR_get_error(), NULL));
+          }
+        } else {
+          debug(1,
+                "error \"%s\" with EVP_PKEY_sign_init:", ERR_error_string(ERR_get_error(), NULL));
+        }
+      } break;
+      case RSA_MODE_KEY: {
+        if (EVP_PKEY_decrypt_init(ctx) > 0) {
+          if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) > 0) {
+            /* Determine buffer length */
+            if (EVP_PKEY_decrypt(ctx, NULL, &ol, (const unsigned char *)input, inlen) > 0) {
+              out = OPENSSL_malloc(ol);
+              if (out != NULL) {
+                if (EVP_PKEY_decrypt(ctx, out, &ol, (const unsigned char *)input, inlen) > 0) {
+                  debug(3, "decrypt success");
+                } else {
+                  debug(1, "error \"%s\" with EVP_PKEY_decrypt:",
+                        ERR_error_string(ERR_get_error(), NULL));
+                }
+              } else {
+                debug(1, "OPENSSL_malloc failed");
+              }
+            } else {
+              debug(1,
+                    "error \"%s\" with EVP_PKEY_decrypt:", ERR_error_string(ERR_get_error(), NULL));
+            }
+          } else {
+            debug(1, "error \"%s\" with EVP_PKEY_CTX_set_rsa_padding:",
+                  ERR_error_string(ERR_get_error(), NULL));
+          }
+        } else {
+          debug(1, "error \"%s\" with EVP_PKEY_decrypt_init:",
+                ERR_error_string(ERR_get_error(), NULL));
+        }
+      } break;
+      default:
+        debug(1, "Unknown mode");
+        break;
+      }
+      EVP_PKEY_CTX_free(ctx); // 1.0.2
+    } else {
+      printf("error \"%s\" with EVP_PKEY_CTX_new:\n", ERR_error_string(ERR_get_error(), NULL));
+    }
+    EVP_PKEY_free(rsaKey); // 1.0.2
+  } else {
+    printf("error \"%s\" with EVP_PKEY_new:\n", ERR_error_string(ERR_get_error(), NULL));
   }
-  RSA_free(rsa);
+  *outlen = ol;
   pthread_setcancelstate(oldState, NULL);
   return out;
 }
@@ -741,10 +1050,10 @@ void command_set_volume(double volume) {
           execv(argV[0], argV);
           warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume);
           // debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume);
-          exit(EXIT_FAILURE); /* only if execv fails */
+          _exit(EXIT_FAILURE); /* only if execv fails */
         }
       }
-
+      _exit(EXIT_SUCCESS);
     } else {
       if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
                                     process to finish */
@@ -796,7 +1105,7 @@ void command_start(void) {
         execv(argV[0], argV);
         warn("Execution of on-start command failed to start");
         debug(1, "Error executing on-start command %s", config.cmd_start);
-        exit(EXIT_FAILURE); /* only if execv fails */
+        _exit(EXIT_FAILURE); /* only if execv fails */
       }
     } else {
       if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process
@@ -830,7 +1139,7 @@ void command_start(void) {
 void command_execute(const char *command, const char *extra_argument, const int block) {
   // this has a cancellation point if waiting is enabled
   if (command) {
-    char new_command_buffer[1024];
+    char new_command_buffer[2048];
     char *full_command = (char *)command;
     if (extra_argument != NULL) {
       memset(new_command_buffer, 0, sizeof(new_command_buffer));
@@ -852,7 +1161,7 @@ void command_execute(const char *command, const char *extra_argument, const int
         execv(argV[0], argV);
         warn("Execution of command \"%s\" failed to start", full_command);
         debug(1, "Error executing command \"%s\".", full_command);
-        exit(EXIT_FAILURE); /* only if execv fails */
+        _exit(EXIT_FAILURE); /* only if execv fails */
       }
     } else {
       if (block) { /* pid!=0 means parent process and if blocking is true, wait for
@@ -883,7 +1192,25 @@ uint32_t uatoi(const char *nptr) {
   return r;
 }
 
+// clang-format off
+
+// Given an AirPlay volume (0 to -30) and the highest and lowest attenuations available in the mixer,
+// the *vol2attn functions return anmattenuation depending on the AirPlay volume
+// and the function's transfer function.
+
+// Note that the max_db and min_db are given as dB*100
+
+// clang-format on
+
 double flat_vol2attn(double vol, long max_db, long min_db) {
+  // clang-format off
+
+// This "flat" volume control profile has the property that a given change in the AirPlay volume
+// always results in the same change in output dB. For example, if a change of AirPlay volume
+// from 0 to -4 resulted in a 7 dB change, then a change in AirPlay volume from -20 to -24
+// would also result in a 7 dB change.
+
+  // clang-format on
   double vol_setting = min_db; // if all else fails, set this, for safety
 
   if ((vol <= 0.0) && (vol >= -30.0)) {
@@ -892,21 +1219,77 @@ double flat_vol2attn(double vol, long max_db, long min_db) {
     // max_db);
   } else if (vol != -144.0) {
     debug(1,
-          "Linear volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
+          "flat_vol2attn volume request value %f is out of range: should be from 0.0 to -30.0 or "
+          "-144.0.",
           vol);
   }
   return vol_setting;
 }
-// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an
-// attenuation depending on the volume and the function's transfer function
-// See http://tangentsoft.net/audio/atten.html for data on good attenuators.
-// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer
-// functions referred to at the link above.
 
-// Note that the max_db and min_db are given as dB*100
+double dasl_tapered_vol2attn(double vol, long max_db, long min_db) {
+  // clang-format off
+
+// The "dasl_tapered" volume control profile has the property that halving the AirPlay volume (the "vol" parameter)
+// reduces the output level by 10 dB, which corresponds to roughly halving the perceived volume.
+
+// For example, if the AirPlay volume goes from 0.0 to -15.0, the output level will decrease by 10 dB.
+// Halving the AirPlay volume again, from -15 to -22.5, will decrease output by a further 10 dB.
+// Reducing the AirPlay volume by half again, this time from -22.5 to -25.25 decreases the output by a further 10 dB,
+// meaning that at AirPlay volume -25.25, the volume is decreased 30 dB.
+
+// If the attenuation range of the mixer is restricted -- for example, if it is just 30 dB --
+// the output level would reach its minimum before the AirPlay volume reached its minimum.
+// This would result in part of the AirPlay volume control's range where
+// changing the AirPlay volume would make no difference to the output level.
+
+// In the example of an attenuator with a range of 00.dB to -30.0dB, this
+// "dead zone" would be from AirPlay volume -30.0 to -25.25,
+// i.e. about one sixth of its -30.0 to 0.0 travel.
+
+// To work around this, the "flat" output level is used if it gives a
+// higher output dB value than the calculation described above.
+// If the device's attenuation range is over about 50 dB,
+// the flat output level will hardly be needed at all.
+
+  // clang-format on
+  double vol_setting = min_db; // if all else fails, set this, for safety
+
+  if ((vol <= 0.0) && (vol >= -30.0)) {
+    double vol_pct = 1 - (vol / -30.0); // This will be in the range [0, 1]
+    if (vol_pct <= 0) {
+      return min_db;
+    }
+
+    double flat_setting = min_db + (max_db - min_db) * vol_pct;
+    vol_setting =
+        max_db + 1000 * log10(vol_pct) / log10(2); // This will be in the range [-inf, max_db]
+    if (vol_setting < flat_setting) {
+      debug(3,
+            "dasl_tapered_vol2attn returning a flat setting of %f for AirPlay volume %f instead of "
+            "a tapered setting of %f in a range from %f to %f.",
+            flat_setting, vol, vol_setting, 1.0 * min_db, 1.0 * max_db);
+      return flat_setting;
+    }
+    if (vol_setting > max_db) {
+      return max_db;
+    }
+    return vol_setting;
+  } else if (vol != -144.0) {
+    debug(1,
+          "dasl_tapered volume request value %f is out of range: should be from 0.0 to -30.0 or "
+          "-144.0.",
+          vol);
+  }
+  return vol_setting;
+}
 
 double vol2attn(double vol, long max_db, long min_db) {
 
+  // See http://tangentsoft.net/audio/atten.html for data on good attenuators.
+
+  // We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer
+  // functions referred to at the link above.
+
   // We use a little coordinate geometry to build a transfer function from the volume passed in to
   // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
   // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
@@ -938,6 +1321,8 @@ double vol2attn(double vol, long max_db, long min_db) {
     int i;
     for (i = 0; i < order; i++) {
       if (vol <= lines[i][0]) {
+        if ((-30 - lines[i][0]) == 0.0)
+          die("(-30 - lines[%d][0]) == 0.0!", i);
         double tvol = lines[i][1] * (vol - lines[i][0]) / (-30 - lines[i][0]);
         // debug(1,"On line %d, end point of %f, input vol %f yields output vol
         // %f.",i,lines[i][1],vol,tvol);
@@ -947,7 +1332,7 @@ double vol2attn(double vol, long max_db, long min_db) {
     }
     vol_setting += max_db;
   } else if (vol != -144.0) {
-    debug(1, "Volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
+    debug(1, "vol2attn request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
           vol);
     vol_setting = min_db; // for safety, return the lowest setting...
   } else {
@@ -959,29 +1344,24 @@ double vol2attn(double vol, long max_db, long min_db) {
   return vol_setting;
 }
 
-uint64_t get_absolute_time_in_fp() {
-  uint64_t time_now_fp;
+uint64_t get_monotonic_time_in_ns() {
+  uint64_t time_now_ns;
+
 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
   struct timespec tn;
-  // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
   clock_gettime(CLOCK_MONOTONIC, &tn);
-  uint64_t tnfpsec = tn.tv_sec;
-  if (tnfpsec > 0x100000000)
-    warn("clock_gettime seconds overflow!");
-  uint64_t tnfpnsec = tn.tv_nsec;
-  if (tnfpnsec > 0x100000000)
-    warn("clock_gettime nanoseconds seconds overflow!");
-  tnfpsec = tnfpsec << 32;
-  tnfpnsec = tnfpnsec << 32;
-  tnfpnsec = tnfpnsec / 1000000000;
-
-  time_now_fp = tnfpsec + tnfpnsec; // types okay
+  uint64_t tnnsec = tn.tv_sec;
+  tnnsec = tnnsec * 1000000000;
+  uint64_t tnjnsec = tn.tv_nsec;
+  time_now_ns = tnnsec + tnjnsec;
 #endif
+
 #ifdef COMPILE_FOR_OSX
   uint64_t time_now_mach;
-  uint64_t elapsedNano;
   static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
 
+  // this actually give you a monotonic clock
+  // see https://news.ycombinator.com/item?id=6303755
   time_now_mach = mach_absolute_time();
 
   // If this is the first time we've run, get the timebase.
@@ -994,28 +1374,44 @@ uint64_t get_absolute_time_in_fp() {
     (void)mach_timebase_info(&sTimebaseInfo);
   }
 
+  if (sTimebaseInfo.denom == 0)
+    die("could not initialise Mac timebase info in get_monotonic_time_in_ns().");
+
   // Do the maths. We hope that the multiplication doesn't
   // overflow; the price you pay for working in fixed point.
 
   // this gives us nanoseconds
-  uint64_t time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
+  time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
+#endif
 
-  // take the units and shift them to the upper half of the fp, and take the nanoseconds, shift them
-  // to the upper half and then divide the result to 1000000000
-  time_now_fp =
-      ((time_now_ns / 1000000000) << 32) + (((time_now_ns % 1000000000) << 32) / 1000000000);
+  return time_now_ns;
+}
 
-#endif
-  return time_now_fp;
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+// Not defined for macOS
+uint64_t get_realtime_in_ns() {
+  uint64_t time_now_ns;
+  struct timespec tn;
+  clock_gettime(CLOCK_REALTIME, &tn);
+  uint64_t tnnsec = tn.tv_sec;
+  tnnsec = tnnsec * 1000000000;
+  uint64_t tnjnsec = tn.tv_nsec;
+  time_now_ns = tnnsec + tnjnsec;
+  return time_now_ns;
 }
+#endif
 
 uint64_t get_absolute_time_in_ns() {
+  // CLOCK_MONOTONIC_RAW/CLOCK_MONOTONIC in Linux/FreeBSD etc, monotonic in MacOSX
   uint64_t time_now_ns;
 
 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
   struct timespec tn;
-  // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
+#ifdef CLOCK_MONOTONIC_RAW
+  clock_gettime(CLOCK_MONOTONIC_RAW, &tn);
+#else
   clock_gettime(CLOCK_MONOTONIC, &tn);
+#endif
   uint64_t tnnsec = tn.tv_sec;
   tnnsec = tnnsec * 1000000000;
   uint64_t tnjnsec = tn.tv_nsec;
@@ -1024,9 +1420,9 @@ uint64_t get_absolute_time_in_ns() {
 
 #ifdef COMPILE_FOR_OSX
   uint64_t time_now_mach;
-  uint64_t elapsedNano;
   static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
 
+  // this actually give you a monotonic clock
   time_now_mach = mach_absolute_time();
 
   // If this is the first time we've run, get the timebase.
@@ -1042,6 +1438,9 @@ uint64_t get_absolute_time_in_ns() {
   // Do the maths. We hope that the multiplication doesn't
   // overflow; the price you pay for working in fixed point.
 
+  if (sTimebaseInfo.denom == 0)
+    die("could not initialise Mac timebase info in get_absolute_time_in_ns().");
+
   // this gives us nanoseconds
   time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
 #endif
@@ -1049,82 +1448,37 @@ uint64_t get_absolute_time_in_ns() {
   return time_now_ns;
 }
 
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, uint64_t timeout) {
-       // try to write to this non-blocking file descriptor
-       // wait for up to the timeout for it to be possible to write something.
-       // exit immediately if there is no connection at the other end
-  // timeout is in nanoseconds
-  void *ibuf = (void *)buf;
-  size_t bytes_remaining = count;
-  ssize_t rc = 0;
-  uint64_t end_time = get_absolute_time_in_ns() + timeout;
-  int timed_out = 0;
-  while ((bytes_remaining) && (rc == 0)) {
-       rc = write(fd, ibuf, bytes_remaining);
-       if (rc >= 0) {
-               bytes_remaining = bytes_remaining - rc;
-               ibuf += rc;
-               rc = 0; // not an error
-       } else if (errno == EAGAIN) {
-                       // delay for a little while if it couldn't get everything out...
-                       uint64_t time_remaining = end_time - get_absolute_time_in_ns();
-                       useconds_t t = time_remaining / 1000; // ns to us
-                       if (t > 50000)
-                               t = 50000; // wait for 50 milliseconds at most
-                       if (t != 0) {
-                               rc = 0; // not an error
-                               usleep(t);
-                       }
-               } else {
-                       debug(2,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
-               }
-  }
-  if (rc > 0)
-    return count - bytes_remaining; // this is just to mimic a normal write/3.
-  else
-    return rc;
-}
-
-/*
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
-       //
-  // timeout is in milliseconds
-  void *ibuf = (void *)buf;
-  size_t bytes_remaining = count;
-  int rc = 1;
-  struct pollfd ufds[1];
-  while ((bytes_remaining > 0) && (rc > 0)) {
-    // check that we can do some writing
-    ufds[0].fd = fd;
-    ufds[0].events = POLLOUT;
-    rc = poll(ufds, 1, timeout);
-    if (rc < 0) {
-      // debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
-    } else if (rc == 0) {
-      // warn("non-blocking write timeout waiting for pipe to become ready for writing");
-      rc = -1;
-      errno = -ETIMEDOUT;
-    } else { // rc > 0, implying it might be ready
-      ssize_t bytes_written = write(fd, ibuf, bytes_remaining);
-      if (bytes_written == -1) {
-        // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
-        rc = bytes_written; // to imitate the return from write()
-      } else {
-        ibuf += bytes_written;
-        bytes_remaining -= bytes_written;
+int try_to_open_pipe_for_writing(const char *pathname) {
+  // tries to open the pipe in non-blocking mode first.
+  // if it succeeds, it sets it to blocking.
+  // if not, it returns -1.
+
+  int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
+
+  // 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.
+  // This is checked by the caller.
+
+  if (fdis >= 0) {
+    // now we switch to blocking mode
+    int flags = fcntl(fdis, F_GETFL);
+    if (flags == -1) {
+      char errorstring[1024];
+      strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+      debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+            (char *)errorstring, pathname);
+    } else {
+      flags = fcntl(fdis, F_SETFL, flags & ~O_NONBLOCK);
+      if (flags == -1) {
+        char errorstring[1024];
+        strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+        debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+              (char *)errorstring, pathname);
       }
     }
   }
-  if (rc > 0)
-    return count - bytes_remaining; // this is just to mimic a normal write/3.
-  else
-    return rc;
-  //  return write(fd,buf,count);
-}
-*/
-
-ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
-  return non_blocking_write_with_timeout(fd, buf, count, 500000000); // default is 0.5 seconds.
+  return fdis;
 }
 
 /* from
@@ -1221,6 +1575,16 @@ uint16_t nctohs(const uint8_t *p) { // read 2 characters from *p and do ntohs on
   return ntohs(holder);
 }
 
+uint64_t nctoh64(const uint8_t *p) {
+  uint32_t landing = nctohl(p); // get the high order 32 bits
+  uint64_t vl = landing;
+  vl = vl << 32;                          // shift them into the correct location
+  landing = nctohl(p + sizeof(uint32_t)); // and the low order 32 bits
+  uint64_t ul = landing;
+  vl = vl + ul;
+  return vl;
+}
+
 pthread_mutex_t barrier_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 void memory_barrier() {
@@ -1244,64 +1608,30 @@ void sps_nanosleep(const time_t sec, const long nanosec) {
 // Mac OS X doesn't have pthread_mutex_timedlock
 // Also note that timing must be relative to CLOCK_REALTIME
 
+/*
 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
-int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
-                                const char *debugmessage, int debuglevel) {
+int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time) {
 
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  struct timespec tn;
-  clock_gettime(CLOCK_REALTIME, &tn);
-  uint64_t tnfpsec = tn.tv_sec;
-  if (tnfpsec > 0x100000000)
-    warn("clock_gettime seconds overflow!");
-  uint64_t tnfpnsec = tn.tv_nsec;
-  if (tnfpnsec > 0x100000000)
-    warn("clock_gettime nanoseconds seconds overflow!");
-  tnfpsec = tnfpsec << 32;
-  tnfpnsec = tnfpnsec << 32;
-  tnfpnsec = tnfpnsec / 1000000000;
-
-  uint64_t time_now_in_fp = tnfpsec + tnfpnsec; // types okay
-
-  uint64_t dally_time_in_fp = dally_time;                // microseconds
-  dally_time_in_fp = (dally_time_in_fp << 32) / 1000000; // convert to fp format
-  uint64_t time_then = time_now_in_fp + dally_time_in_fp;
-
-  uint64_t time_then_nsec = time_then & 0xffffffff; // remove integral part
-  time_then_nsec = time_then_nsec * 1000000000;     // multiply fractional part to nanoseconds
-
   struct timespec timeoutTime;
-
-  time_then = time_then >> 32;           // get the seconds
-  time_then_nsec = time_then_nsec >> 32; // and the nanoseconds
-
-  timeoutTime.tv_sec = time_then;
-  timeoutTime.tv_nsec = time_then_nsec;
-  uint64_t start_time = get_absolute_time_in_ns();
+  uint64_t wait_until_time = dally_time * 1000; // to nanoseconds
+  uint64_t start_time = get_realtime_in_ns();   // this is from CLOCK_REALTIME
+  wait_until_time = wait_until_time + start_time;
+  uint64_t wait_until_sec = wait_until_time / 1000000000;
+  uint64_t wait_until_nsec = wait_until_time % 1000000000;
+  timeoutTime.tv_sec = wait_until_sec;
+  timeoutTime.tv_nsec = wait_until_nsec;
   int r = pthread_mutex_timedlock(mutex, &timeoutTime);
-  uint64_t et = get_absolute_time_in_ns() - start_time;
-
-  if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
-    char errstr[1000];
-    if (r == ETIMEDOUT)
-      debug(debuglevel,
-            "timed out waiting for a mutex, having waited %f microseconds, with a maximum "
-            "waiting time of %d microseconds. \"%s\".",
-            (1.0E6 * et) / 1000000000, dally_time, debugmessage);
-    else
-      debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
-            strerror_r(r, errstr, sizeof(errstr)), debugmessage);
-  }
   pthread_setcancelstate(oldState, NULL);
   return r;
 }
 #endif
 #ifdef COMPILE_FOR_OSX
-int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
-                                const char *debugmessage, int debuglevel) {
+*/
+int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time) {
 
-  // this is not pthread_cancellation safe because is contains a cancellation point
+  // this would not be not pthread_cancellation safe because is contains a cancellation point
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
   int time_to_wait = dally_time;
@@ -1314,22 +1644,10 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
     time_to_wait -= st;
     r = pthread_mutex_trylock(mutex);
   }
-  if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
-    char errstr[1000];
-    if (r == EBUSY) {
-      debug(debuglevel,
-            "waiting for a mutex, maximum expected time of %d microseconds exceeded \"%s\".",
-            dally_time, debugmessage);
-      r = ETIMEDOUT; // for compatibility
-    } else {
-      debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
-            strerror_r(r, errstr, sizeof(errstr)), debugmessage);
-    }
-  }
   pthread_setcancelstate(oldState, NULL);
   return r;
 }
-#endif
+// #endif
 
 int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname,
                       const char *filename, const int line, int debuglevel) {
@@ -1337,19 +1655,20 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
     return pthread_mutex_lock(mutex);
   int oldState;
   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
-  uint64_t time_at_start = get_absolute_time_in_ns();
-  char dstring[1000];
-  memset(dstring, 0, sizeof(dstring));
-  snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
   if (debuglevel != 0)
-    debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); // only if you really ask for it!
-  int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
+    _debug(filename, line, 3, "mutex_lock \"%s\".", mutexname); // only if you really ask for it!
+  int result = sps_pthread_mutex_timedlock(mutex, dally_time);
   if (result == ETIMEDOUT) {
+    _debug(
+        filename, line, debuglevel,
+        "mutex_lock \"%s\" failed to lock after %f ms -- now waiting unconditionally to lock it.",
+        mutexname, dally_time * 1E-3);
     result = pthread_mutex_lock(mutex);
-    uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
-    debug(debuglevel,
-          "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f microseconds.",
-          mutexname, dstring, (1.0 * dally_time), 0.001 * time_delay);
+    if (result == 0)
+      _debug(filename, line, debuglevel, " ...mutex_lock \"%s\" locked successfully.", mutexname);
+    else
+      _debug(filename, line, debuglevel, " ...mutex_lock \"%s\" exited with error code: %u",
+             mutexname, result);
   }
   pthread_setcancelstate(oldState, NULL);
   return result;
@@ -1375,9 +1694,48 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha
 }
 
 void malloc_cleanup(void *arg) {
-  // debug(1, "malloc cleanup called.");
+  // debug(1, "malloc cleanup freeing %" PRIxPTR ".", arg);
   free(arg);
-  arg = NULL;
+}
+
+#ifdef CONFIG_AIRPLAY_2
+void plist_cleanup(void *arg) {
+  // debug(1, "plist cleanup called.");
+  plist_free((plist_t)arg);
+}
+#endif
+
+void socket_cleanup(void *arg) {
+  intptr_t fdp = (intptr_t)arg;
+  debug(3, "socket_cleanup called for socket: %" PRIdPTR ".", fdp);
+  close(fdp);
+}
+
+void cv_cleanup(void *arg) {
+  // debug(1, "cv_cleanup called.");
+  pthread_cond_t *cv = (pthread_cond_t *)arg;
+  pthread_cond_destroy(cv);
+}
+
+void mutex_cleanup(void *arg) {
+  // debug(1, "mutex_cleanup called.");
+  pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
+  pthread_mutex_destroy(mutex);
+}
+
+void mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
+
+void rwlock_unlock(void *arg) { pthread_rwlock_unlock((pthread_rwlock_t *)arg); }
+
+void thread_cleanup(void *arg) {
+  debug(3, "thread_cleanup called.");
+  pthread_t *thread = (pthread_t *)arg;
+  pthread_cancel(*thread);
+  int oldState;
+  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+  pthread_join(*thread, NULL);
+  pthread_setcancelstate(oldState, NULL);
+  debug(3, "thread_cleanup done.");
 }
 
 void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
@@ -1385,8 +1743,21 @@ void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthre
 char *get_version_string() {
   char *version_string = malloc(1024);
   if (version_string) {
-    strcpy(version_string, PACKAGE_VERSION);
-
+#ifdef CONFIG_USE_GIT_VERSION_STRING
+    if (git_version_string[0] != '\0')
+      strcpy(version_string, git_version_string);
+    else
+#endif
+      strcpy(version_string, PACKAGE_VERSION);
+#ifdef CONFIG_AIRPLAY_2
+    strcat(version_string, "-AirPlay2");
+    char smiv[1024];
+    snprintf(smiv, 1024, "-smi%u", NQPTP_SHM_STRUCTURES_VERSION);
+    strcat(version_string, smiv);
+#endif
+#ifdef CONFIG_APPLE_ALAC
+    strcat(version_string, "-alac");
+#endif
 #ifdef CONFIG_LIBDAEMON
     strcat(version_string, "-libdaemon");
 #endif
@@ -1417,12 +1788,18 @@ char *get_version_string() {
 #ifdef CONFIG_SNDIO
     strcat(version_string, "-sndio");
 #endif
+#ifdef CONFIG_JACK
+    strcat(version_string, "-jack");
+#endif
 #ifdef CONFIG_AO
     strcat(version_string, "-ao");
 #endif
 #ifdef CONFIG_PA
     strcat(version_string, "-pa");
 #endif
+#ifdef CONFIG_PW
+    strcat(version_string, "-pw");
+#endif
 #ifdef CONFIG_SOUNDIO
     strcat(version_string, "-soundio");
 #endif
@@ -1662,3 +2039,105 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
   }
   return *flag;
 }
+
+// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
+void *memdup(const void *mem, size_t size) {
+  void *out = malloc(size);
+
+  if (out != NULL)
+    memcpy(out, mem, size);
+
+  return out;
+}
+
+// This will allocate memory and place the NUL-terminated hex character equivalent of
+// the bytearray passed in whose length is given.
+char *debug_malloc_hex_cstring(void *packet, size_t nread) {
+  char *response = malloc(nread * 3 + 1);
+  unsigned char *q = packet;
+  char *obfp = response;
+  size_t obfc;
+  for (obfc = 0; obfc < nread; obfc++) {
+    snprintf(obfp, 4, "%02x ", *q);
+    obfp += 3; // two digit characters and a space
+    q++;
+  };
+  obfp--; // overwrite the last space with a NUL
+  *obfp = 0;
+  return response;
+}
+
+// the difference between two unsigned 32-bit modulo values as a signed 32-bit result
+// now, if the two numbers are constrained to be within 2^(n-1)-1 of one another,
+// we can use their as a signed 2^n bit number which will be positive
+// if the first number is the same or "after" the second, and
+// negative otherwise
+
+int32_t mod32Difference(uint32_t a, uint32_t b) {
+  int32_t result = a - b;
+  return result;
+}
+
+int get_device_id(uint8_t *id, int int_length) {
+
+  uint64_t wait_time = 10000000000L; // wait up to this (ns) long to get a MAC address
+
+  int response = -1;
+  struct ifaddrs *ifaddr = NULL;
+  struct ifaddrs *ifa = NULL;
+
+  int i = 0;
+  uint8_t *t = id;
+  for (i = 0; i < int_length; i++) {
+    *t++ = 0;
+  }
+
+  uint64_t wait_until = get_absolute_time_in_ns();
+  wait_until = wait_until + wait_time;
+
+  int64_t time_to_wait;
+  do {
+    if (getifaddrs(&ifaddr) == 0) {
+      t = id;
+      int found = 0;
+
+      for (ifa = ifaddr; ((ifa != NULL) && (found == 0)); ifa = ifa->ifa_next) {
+#ifdef AF_PACKET
+        if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
+          struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
+          if ((strcmp(ifa->ifa_name, "lo") != 0)) {
+            found = 1;
+            response = 0;
+            for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) {
+              *t++ = s->sll_addr[i];
+            }
+          }
+        }
+#else
+#ifdef AF_LINK
+        struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+        if ((sdl) && (sdl->sdl_family == AF_LINK)) {
+          if (sdl->sdl_type == IFT_ETHER) {
+            found = 1;
+            response = 0;
+            uint8_t *s = (uint8_t *)LLADDR(sdl);
+            for (i = 0; ((i < sdl->sdl_alen) && (i < int_length)); i++) {
+              *t++ = *s++;
+            }
+          }
+        }
+#endif
+#endif
+      }
+      freeifaddrs(ifaddr);
+    }
+    // wait a little time if we haven't got a response
+    if (response != 0) {
+      usleep(100000);
+    }
+    time_to_wait = wait_until - get_absolute_time_in_ns();
+  } while ((response != 0) && (time_to_wait > 0));
+  if (response != 0)
+    warn("Can't create a device ID -- no valid MAC address can be found.");
+  return response;
+}