/*
 * 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
 * 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 "common.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <memory.h>
#include <poll.h>
#include <popt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#ifdef COMPILE_FOR_OSX
#include <CoreServices/CoreServices.h>
#include <mach/mach.h>
#include <mach/mach_time.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>
#endif

#ifdef CONFIG_POLARSSL
#include "polarssl/ctr_drbg.h"
#include "polarssl/entropy.h"
#include <polarssl/base64.h>
#include <polarssl/md.h>
#include <polarssl/version.h>
#include <polarssl/x509.h>

#if POLARSSL_VERSION_NUMBER >= 0x01030000
#include "polarssl/compat-1.2.h"
#endif
#endif

#ifdef CONFIG_MBEDTLS
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include <mbedtls/base64.h>
#include <mbedtls/md.h>
#include <mbedtls/version.h>
#include <mbedtls/x509.h>
#endif

#ifdef CONFIG_LIBDAEMON
#include <libdaemon/dlog.h>
#else
#include <syslog.h>
#endif

#ifdef CONFIG_ALSA
void set_alsa_out_dev(char *);
#endif

config_t config_file_stuff;
int emergency_exit;
pthread_t main_thread_id;
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
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;

const char *sps_format_description_string_array[] = {
    "unknown", "S8",      "U8",      "S16", "S16_LE", "S16_BE", "S24",  "S24_LE",
    "S24_BE",  "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};

const char *sps_format_description_string(sps_format_t format) {
  if (format <= SPS_FORMAT_AUTO)
    return sps_format_description_string_array[format];
  else
    return sps_format_description_string_array[SPS_FORMAT_INVALID];
}

// true if Shairport Sync is supposed to be sending output to the output device, false otherwise

static volatile int requested_connection_state_to_output = 1;

// this stuff is to direct logging to syslog via libdaemon or directly
// alternatively you can direct it to stderr using a command line option

#ifdef CONFIG_LIBDAEMON
static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
#else
static void (*sps_log)(int prio, const char *t, ...) = syslog;
#endif

void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
  char s[1024];
  va_list args;
  va_start(args, t);
  vsnprintf(s, sizeof(s), t, args);
  va_end(args);
  fprintf(stderr, "%s\n", s);
}

void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
  char s[1024];
  va_list args;
  va_start(args, t);
  vsnprintf(s, sizeof(s), t, args);
  va_end(args);
  fprintf(stdout, "%s\n", s);
}

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;
}

void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
  char s[1024];
  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 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;

// accessors for multi-thread-access fields in the conn structure

double get_config_airplay_volume() {
  config_lock;
  double v = config.airplay_volume;
  config_unlock;
  return v;
}

void set_config_airplay_volume(double v) {
  config_lock;
  config.airplay_volume = v;
  config_unlock;
}

volatile int debuglev = 0;

sigset_t pselect_sigset;

int usleep_uncancellable(useconds_t usec) {
  int response;
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  response = usleep(usec);
  pthread_setcancelstate(oldState, NULL);
  return response;
}

static uint16_t UDPPortIndex = 0;

void resetFreeUDPPort() {
  debug(3, "Resetting UDP Port Suggestion to %u", config.udp_port_base);
  UDPPortIndex = 0;
}

uint16_t nextFreeUDPPort() {
  if (UDPPortIndex == 0)
    UDPPortIndex = config.udp_port_base;
  else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1))
    UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can
                                             // be assigned by resetFreeUDPPort without checking
  else
    UDPPortIndex++;
  return UDPPortIndex;
}

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);
    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);
    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);
    insertion_point = insertion_point + strlen(insertion_point);
    space_remaining = space_remaining - strlen(insertion_point);
  }
  if (prefix) {
    snprintf(insertion_point, space_remaining, "%s", prefix);
    insertion_point = insertion_point + strlen(insertion_point);
    space_remaining = space_remaining - strlen(insertion_point);
  }
  return insertion_point;
}

void _die(const char *filename, const int linenumber, const char *format, ...) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  char b[1024];
  b[0] = 0;
  char *s;
  if (debuglev) {
    pthread_mutex_lock(&debug_timing_lock);
    uint64_t time_now = get_absolute_time_in_ns();
    uint64_t time_since_start = time_now - ns_time_at_startup;
    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);
    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: ");
  } else {
    strncpy(b, "fatal error: ", sizeof(b));
    s = b + strlen(b);
  }
  va_list args;
  va_start(args, format);
  vsnprintf(s, sizeof(b) - (s - b), format, args);
  va_end(args);
  sps_log(LOG_ERR, "%s", b);
  pthread_setcancelstate(oldState, NULL);
  emergency_exit = 1;
  exit(EXIT_FAILURE);
}

void _warn(const char *filename, const int linenumber, const char *format, ...) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  char b[1024];
  b[0] = 0;
  char *s;
  if (debuglev) {
    pthread_mutex_lock(&debug_timing_lock);
    uint64_t time_now = get_absolute_time_in_ns();
    uint64_t time_since_start = time_now - ns_time_at_startup;
    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);
    s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                    1.0 * time_since_last_debug_message / 1000000000, filename,
                                    linenumber, " *warning: ");
  } else {
    strncpy(b, "warning: ", sizeof(b));
    s = b + strlen(b);
  }
  va_list args;
  va_start(args, format);
  vsnprintf(s, sizeof(b) - (s - b), format, args);
  va_end(args);
  sps_log(LOG_WARNING, "%s", b);
  pthread_setcancelstate(oldState, NULL);
}

void _debug(const char *filename, const int linenumber, int level, const char *format, ...) {
  if (level > debuglev)
    return;
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  char b[1024];
  b[0] = 0;
  pthread_mutex_lock(&debug_timing_lock);
  uint64_t time_now = get_absolute_time_in_ns();
  uint64_t time_since_start = time_now - ns_time_at_startup;
  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 *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                        1.0 * time_since_last_debug_message / 1000000000, filename,
                                        linenumber, " ");
  va_list args;
  va_start(args, format);
  vsnprintf(s, sizeof(b) - (s - b), format, args);
  va_end(args);
  sps_log(LOG_DEBUG, "%s", b);
  pthread_setcancelstate(oldState, NULL);
}

void _inform(const char *filename, const int linenumber, const char *format, ...) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  char b[1024];
  b[0] = 0;
  char *s;
  if (debuglev) {
    pthread_mutex_lock(&debug_timing_lock);
    uint64_t time_now = get_absolute_time_in_ns();
    uint64_t time_since_start = time_now - ns_time_at_startup;
    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);
    s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
                                    1.0 * time_since_last_debug_message / 1000000000, filename,
                                    linenumber, " ");
  } else {
    s = b;
  }
  va_list args;
  va_start(args, format);
  vsnprintf(s, sizeof(b) - (s - b), format, args);
  va_end(args);
  sps_log(LOG_INFO, "%s", b);
  pthread_setcancelstate(oldState, NULL);
}

// The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample
// code at
// https://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux

int do_mkdir(const char *path, mode_t mode) {
  struct stat st;
  int status = 0;

  if (stat(path, &st) != 0) {
    /* Directory does not exist. EEXIST for race condition */
    if (mkdir(path, mode) != 0 && errno != EEXIST)
      status = -1;
  } else if (!S_ISDIR(st.st_mode)) {
    errno = ENOTDIR;
    status = -1;
  }

  return (status);
}

// mkpath - ensure all directories in path exist
// Algorithm takes the pessimistic view and works top-down to ensure
// each directory in path exists, rather than optimistically creating
// the last element and working backwards.

int mkpath(const char *path, mode_t mode) {
  char *pp;
  char *sp;
  int status;
  char *copypath = strdup(path);

  status = 0;
  pp = copypath;
  while (status == 0 && (sp = strchr(pp, '/')) != 0) {
    if (sp != pp) {
      /* Neither root nor double slash in path */
      *sp = '\0';
      status = do_mkdir(copypath, mode);
      *sp = '/';
    }
    pp = sp + 1;
  }
  if (status == 0)
    status = do_mkdir(path, mode);
  free(copypath);
  return (status);
}

#ifdef CONFIG_MBEDTLS
char *base64_enc(uint8_t *input, int length) {
  char *buf = NULL;
  size_t dlen = 0;
  int rc = mbedtls_base64_encode(NULL, 0, &dlen, input, length);
  if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL))
    debug(1, "Error %d getting length of base64 encode.", rc);
  else {
    buf = (char *)malloc(dlen);
    rc = mbedtls_base64_encode((unsigned char *)buf, dlen, &dlen, input, length);
    if (rc != 0)
      debug(1, "Error %d encoding base64.", rc);
  }
  return buf;
}

uint8_t *base64_dec(char *input, int *outlen) {
  // slight problem here is that Apple cut the padding off their challenges. We must restore it
  // before passing it in to the decoder, it seems
  uint8_t *buf = NULL;
  size_t dlen = 0;
  int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will
                                                 // send to the decoder, but we need space for 3
                                                 // extra "="s and a NULL
  char *inbuf = malloc(inbufsize + 4);
  if (inbuf == 0)
    debug(1, "Can't malloc memory  for inbuf in base64_decode.");
  else {
    strcpy(inbuf, input);
    strcat(inbuf, "===");
    // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length %d.",
    //		input,strlen(input),inbuf,inbufsize);
    int rc = mbedtls_base64_decode(NULL, 0, &dlen, (unsigned char *)inbuf, inbufsize);
    if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL))
      debug(1, "Error %d getting decode length, result is %d.", rc, dlen);
    else {
      // debug(1,"Decode size is %d.",dlen);
      buf = malloc(dlen);
      if (buf == 0)
        debug(1, "Can't allocate memory in base64_dec.");
      else {
        rc = mbedtls_base64_decode(buf, dlen, &dlen, (unsigned char *)inbuf, inbufsize);
        if (rc != 0)
          debug(1, "Error %d in base64_dec.", rc);
      }
    }
    free(inbuf);
  }
  *outlen = dlen;
  return buf;
}
#endif

#ifdef CONFIG_POLARSSL
char *base64_enc(uint8_t *input, int length) {
  char *buf = NULL;
  size_t dlen = 0;
  int rc = base64_encode(NULL, &dlen, input, length);
  if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
    debug(1, "Error %d getting length of base64 encode.", rc);
  else {
    buf = (char *)malloc(dlen);
    rc = base64_encode((unsigned char *)buf, &dlen, input, length);
    if (rc != 0)
      debug(1, "Error %d encoding base64.", rc);
  }
  return buf;
}

uint8_t *base64_dec(char *input, int *outlen) {
  // slight problem here is that Apple cut the padding off their challenges. We must restore it
  // before passing it in to the decoder, it seems
  uint8_t *buf = NULL;
  size_t dlen = 0;
  int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will
                                                 // send to the decoder, but we need space for 3
                                                 // extra "="s and a NULL
  char *inbuf = malloc(inbufsize + 4);
  if (inbuf == 0)
    debug(1, "Can't malloc memory  for inbuf in base64_decode.");
  else {
    strcpy(inbuf, input);
    strcat(inbuf, "===");
    // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length
    // %d.",input,strlen(input),inbuf,inbufsize);
    int rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize);
    if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
      debug(1, "Error %d getting decode length, result is %d.", rc, dlen);
    else {
      // debug(1,"Decode size is %d.",dlen);
      buf = malloc(dlen);
      if (buf == 0)
        debug(1, "Can't allocate memory in base64_dec.");
      else {
        rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize);
        if (rc != 0)
          debug(1, "Error %d in base64_dec.", rc);
      }
    }
    free(inbuf);
  }
  *outlen = dlen;
  return buf;
}
#endif

#ifdef CONFIG_OPENSSL
char *base64_enc(uint8_t *input, int length) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  BIO *bmem, *b64;
  BUF_MEM *bptr;
  b64 = BIO_new(BIO_f_base64());
  bmem = BIO_new(BIO_s_mem());
  b64 = BIO_push(b64, bmem);
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  BIO_write(b64, input, length);
  (void)BIO_flush(b64);
  BIO_get_mem_ptr(b64, &bptr);

  char *buf = (char *)malloc(bptr->length);
  if (buf == NULL)
    die("could not allocate memory for buf in base64_enc");
  if (bptr->length) {
    memcpy(buf, bptr->data, bptr->length - 1);
    buf[bptr->length - 1] = 0;
  }

  BIO_free_all(b64);

  pthread_setcancelstate(oldState, NULL);
  return buf;
}

uint8_t *base64_dec(char *input, int *outlen) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  BIO *bmem, *b64;
  int inlen = strlen(input);

  b64 = BIO_new(BIO_f_base64());
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  bmem = BIO_new(BIO_s_mem());
  b64 = BIO_push(b64, bmem);

  // Apple cut the padding off their challenges; restore it
  BIO_write(bmem, input, inlen);
  while (inlen++ & 3)
    BIO_write(bmem, "=", 1);
  (void)BIO_flush(bmem);

  int bufsize = strlen(input) * 3 / 4 + 1;
  uint8_t *buf = malloc(bufsize);
  int nread;

  nread = BIO_read(b64, buf, bufsize);

  BIO_free_all(b64);

  *outlen = nread;
  pthread_setcancelstate(oldState, NULL);
  return buf;
}
#endif

static char super_secret_key[] =
    "-----BEGIN RSA PRIVATE KEY-----\n"
    "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
    "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
    "wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
    "/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
    "UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
    "BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
    "LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
    "NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
    "lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
    "aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
    "a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
    "oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
    "oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
    "k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
    "AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
    "cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
    "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
    "17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
    "1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
    "LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
    "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
    "-----END RSA PRIVATE KEY-----\0";

#ifdef CONFIG_OPENSSL
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");
  }
  RSA_free(rsa);
  pthread_setcancelstate(oldState, NULL);
  return out;
}
#endif

#ifdef CONFIG_MBEDTLS
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
  mbedtls_pk_context pkctx;
  mbedtls_rsa_context *trsa;
  const char *pers = "rsa_encrypt";
  size_t olen = *outlen;
  int rc;

  mbedtls_entropy_context entropy;
  mbedtls_ctr_drbg_context ctr_drbg;

  mbedtls_entropy_init(&entropy);

  mbedtls_ctr_drbg_init(&ctr_drbg);
  mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers,
                        strlen(pers));

  mbedtls_pk_init(&pkctx);

  rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key),
                            NULL, 0);
  if (rc != 0)
    debug(1, "Error %d reading the private key.", rc);

  uint8_t *outbuf = NULL;
  trsa = mbedtls_pk_rsa(pkctx);

  switch (mode) {
  case RSA_MODE_AUTH:
    mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
    outbuf = malloc(trsa->len);
    rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
                                   inlen, input, outbuf);
    if (rc != 0)
      debug(1, "mbedtls_pk_encrypt error %d.", rc);
    *outlen = trsa->len;
    break;
  case RSA_MODE_KEY:
    mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    outbuf = malloc(trsa->len);
    rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
                                   &olen, input, outbuf, trsa->len);
    if (rc != 0)
      debug(1, "mbedtls_pk_decrypt error %d.", rc);
    *outlen = olen;
    break;
  default:
    die("bad rsa mode");
  }

  mbedtls_ctr_drbg_free(&ctr_drbg);
  mbedtls_entropy_free(&entropy);
  mbedtls_pk_free(&pkctx);
  return outbuf;
}
#endif

#ifdef CONFIG_POLARSSL
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
  rsa_context trsa;
  const char *pers = "rsa_encrypt";
  int rc;

  entropy_context entropy;
  ctr_drbg_context ctr_drbg;
  entropy_init(&entropy);
  if ((rc = ctr_drbg_init(&ctr_drbg, entropy_func, &entropy, (const unsigned char *)pers,
                          strlen(pers))) != 0)
    debug(1, "ctr_drbg_init returned %d\n", rc);

  rsa_init(&trsa, RSA_PKCS_V21, POLARSSL_MD_SHA1); // padding and hash id get overwritten
  // BTW, this seems to reset a lot of parameters in the rsa_context
  rc = x509parse_key(&trsa, (unsigned char *)super_secret_key, strlen(super_secret_key), NULL, 0);
  if (rc != 0)
    debug(1, "Error %d reading the private key.");

  uint8_t *out = NULL;

  switch (mode) {
  case RSA_MODE_AUTH:
    trsa.padding = RSA_PKCS_V15;
    trsa.hash_id = POLARSSL_MD_NONE;
    debug(2, "rsa_apply encrypt");
    out = malloc(trsa.len);
    rc = rsa_pkcs1_encrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, inlen, input, out);
    if (rc != 0)
      debug(1, "rsa_pkcs1_encrypt error %d.", rc);
    *outlen = trsa.len;
    break;
  case RSA_MODE_KEY:
    debug(2, "rsa_apply decrypt");
    trsa.padding = RSA_PKCS_V21;
    trsa.hash_id = POLARSSL_MD_SHA1;
    out = malloc(trsa.len);
#if POLARSSL_VERSION_NUMBER >= 0x01020900
    rc = rsa_pkcs1_decrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, (size_t *)outlen, input,
                           out, trsa.len);
#else
    rc = rsa_pkcs1_decrypt(&trsa, RSA_PRIVATE, outlen, input, out, trsa.len);
#endif
    if (rc != 0)
      debug(1, "decrypt error %d.", rc);
    break;
  default:
    die("bad rsa mode");
  }
  rsa_free(&trsa);
  debug(2, "rsa_apply exit");
  return out;
}
#endif

int config_set_lookup_bool(config_t *cfg, char *where, int *dst) {
  const char *str = 0;
  if (config_lookup_string(cfg, where, &str)) {
    if (strcasecmp(str, "no") == 0) {
      (*dst) = 0;
      return 1;
    } else if (strcasecmp(str, "yes") == 0) {
      (*dst) = 1;
      return 1;
    } else {
      die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
      return 0;
    }
  } else {
    return 0;
  }
}

void command_set_volume(double volume) {
  // this has a cancellation point if waiting is enabled
  if (config.cmd_set_volume) {
    /*Spawn a child to run the program.*/
    pid_t pid = fork();
    if (pid == 0) { /* child process */
      size_t command_buffer_size = strlen(config.cmd_set_volume) + 32;
      char *command_buffer = (char *)malloc(command_buffer_size);
      if (command_buffer == NULL) {
        inform("Couldn't allocate memory for set_volume argument string");
      } else {
        memset(command_buffer, 0, command_buffer_size);
        snprintf(command_buffer, command_buffer_size, "%s %f", config.cmd_set_volume, volume);
        // debug(1,"command_buffer is \"%s\".",command_buffer);
        int argC;
        char **argV;
        // debug(1,"set_volume command found.");
        if (poptParseArgvString(command_buffer, &argC, (const char ***)&argV) != 0) {
          // note that argV should be free()'d after use, but we expect this fork to exit
          // eventually.
          warn("Can't decipher on-set-volume command arguments \"%s\".", command_buffer);
          free(argV);
          free(command_buffer);
        } else {
          free(command_buffer);
          // debug(1,"Executing on-set-volume command %s with %d arguments.",argV[0],argC);
          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_SUCCESS);
    } else {
      if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
                                    process to finish */
        pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
        if (rc != pid) {
          warn("Execution of on-set-volume command returned an error.");
          debug(1, "on-set-volume command %s finished with error %d", config.cmd_set_volume, errno);
        }
      }
      // debug(1,"Continue after on-set-volume command");
    }
  }
}

void command_start(void) {
  // this has a cancellation point if waiting is enabled or a response is awaited
  if (config.cmd_start) {
    pid_t pid;
    int pipes[2];

    if (config.cmd_start_returns_output && pipe(pipes) != 0) {
      warn("Unable to allocate pipe for popen of start command.");
      debug(1, "pipe finished with error %d", errno);
      return;
    }
    /*Spawn a child to run the program.*/
    pid = fork();
    if (pid == 0) { /* child process */
      int argC;
      char **argV;

      if (config.cmd_start_returns_output) {
        close(pipes[0]);
        if (dup2(pipes[1], 1) < 0) {
          warn("Unable to reopen pipe as stdout for popen of start command");
          debug(1, "dup2 finished with error %d", errno);
          close(pipes[1]);
          return;
        }
      }

      // debug(1,"on-start command found.");
      if (poptParseArgvString(config.cmd_start, &argC, (const char ***)&argV) !=
          0) // note that argV should be free()'d after use, but we expect this fork to exit
             // eventually.
        debug(1, "Can't decipher on-start command arguments");
      else {
        // debug(1,"Executing on-start command %s with %d arguments.",argV[0],argC);
        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 */
      }
    } else {
      if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process
                                    and if blocking is true, wait for
                                    process to finish */
        pid_t rc = waitpid(pid, 0, 0);                              /* wait for child to exit */
        if ((rc != pid) && (errno != ECHILD)) {
          // In this context, ECHILD means that the child process has already completed, I think!
          warn("Execution of on-start command returned an error.");
          debug(1, "on-start command %s finished with error %d", config.cmd_start, errno);
        }
        if (config.cmd_start_returns_output) {
          static char buffer[256];
          int len;
          close(pipes[1]);
          len = read(pipes[0], buffer, 255);
          close(pipes[0]);
          buffer[len] = '\0';
          if (buffer[len - 1] == '\n')
            buffer[len - 1] = '\0'; // strip trailing newlines
          debug(1, "received '%s' as the device to use from the on-start command", buffer);
#ifdef CONFIG_ALSA
          set_alsa_out_dev(buffer);
#endif
        }
      }
      // debug(1,"Continue after on-start command");
    }
  }
}
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 *full_command = (char *)command;
    if (extra_argument != NULL) {
      memset(new_command_buffer, 0, sizeof(new_command_buffer));
      snprintf(new_command_buffer, sizeof(new_command_buffer), "%s %s", command, extra_argument);
      full_command = new_command_buffer;
    }

    /*Spawn a child to run the program.*/
    pid_t pid = fork();
    if (pid == 0) { /* child process */
      int argC;
      char **argV;
      if (poptParseArgvString(full_command, &argC, (const char ***)&argV) !=
          0) // note that argV should be free()'d after use, but we expect this fork to exit
             // eventually.
        debug(1, "Can't decipher command arguments in \"%s\".", full_command);
      else {
        // debug(1,"Executing command %s",full_command);
        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 */
      }
    } else {
      if (block) { /* pid!=0 means parent process and if blocking is true, wait for
                                    process to finish */
        pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
        if ((rc != pid) && (errno != ECHILD)) {
          // In this context, ECHILD means that the child process has already completed, I think!
          warn("Execution of command \"%s\" returned an error.", full_command);
          debug(1, "Command \"%s\" finished with error %d", full_command, errno);
        }
      }
      // debug(1,"Continue after on-unfixable command");
    }
  }
}

void command_stop(void) {
  // this has a cancellation point if waiting is enabled
  if (config.cmd_stop)
    command_execute(config.cmd_stop, "", config.cmd_blocking);
}

// this is for reading an unsigned 32 bit number, such as an RTP timestamp

uint32_t uatoi(const char *nptr) {
  uint64_t llint = atoll(nptr);
  uint32_t r = llint;
  return r;
}

double flat_vol2attn(double vol, long max_db, long min_db) {
  double vol_setting = min_db; // if all else fails, set this, for safety

  if ((vol <= 0.0) && (vol >= -30.0)) {
    vol_setting = ((max_db - min_db) * (30.0 + vol) / 30) + min_db;
    // debug(2, "Linear profile Volume Setting: %f in range %ld to %ld.", vol_setting, 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.",
          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 vol2attn(double vol, long max_db, long min_db) {

  // 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
  // the bottom of the range to the top. We build the transfer function from one or more lines. We
  // characterise each line with two numbers: the first is where on x the line starts when y=0 (x
  // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
  // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
  // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
  // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
  // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
  // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30

#define order 3

  double vol_setting = 0;

  if ((vol <= 0.0) && (vol >= -30.0)) {
    long range_db = max_db - min_db; // this will be a positive number
    // debug(1,"Volume min %ddB, max %ddB, range %ddB.",min_db,max_db,range_db);
    // double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB
    // for the full rotation.
    double first_slope =
        -range_db /
        2; // this is the slope of the attenuation at the high end -- 30dB for the full rotation.
    if (-range_db > first_slope)
      first_slope = range_db;
    double lines[order][2] = {
        {0, first_slope}, {-5, first_slope - (range_db + first_slope) / 2}, {-17, -range_db}};
    int i;
    for (i = 0; i < order; i++) {
      if (vol <= lines[i][0]) {
        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);
        if (tvol < vol_setting)
          vol_setting = tvol;
      }
    }
    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.",
          vol);
    vol_setting = min_db; // for safety, return the lowest setting...
  } else {
    vol_setting = min_db; // for safety, return the lowest setting...
  }
  // debug(1,"returning an attenuation of %f.",vol_setting);
  // debug(2, "Standard profile Volume Setting for Airplay vol %f: %f in range %ld to %ld.", vol,
  //      vol_setting, min_db, max_db);
  return vol_setting;
}

uint64_t get_absolute_time_in_fp() {
  uint64_t time_now_fp;
#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
#endif
#ifdef COMPILE_FOR_OSX
  uint64_t time_now_mach;
  uint64_t elapsedNano;
  static mach_timebase_info_data_t sTimebaseInfo = {0, 0};

  time_now_mach = mach_absolute_time();

  // If this is the first time we've run, get the timebase.
  // We can use denom == 0 to indicate that sTimebaseInfo is
  // uninitialised because it makes no sense to have a zero
  // denominator in a fraction.

  if (sTimebaseInfo.denom == 0) {
    debug(1, "Mac initialise timebase info.");
    (void)mach_timebase_info(&sTimebaseInfo);
  }

  // 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;

  // 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);

#endif
  return time_now_fp;
}

uint64_t get_absolute_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 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};

  time_now_mach = mach_absolute_time();

  // If this is the first time we've run, get the timebase.
  // We can use denom == 0 to indicate that sTimebaseInfo is
  // uninitialised because it makes no sense to have a zero
  // denominator in a fraction.

  if (sTimebaseInfo.denom == 0) {
    debug(1, "Mac initialise timebase info.");
    (void)mach_timebase_info(&sTimebaseInfo);
  }

  // Do the maths. We hope that the multiplication doesn't
  // overflow; the price you pay for working in fixed point.

  // this gives us nanoseconds
  time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
#endif

  return time_now_ns;
}

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);
      }
    }
  }
  return fdis;
}

/* from
 * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
 */

char *str_replace(const char *string, const char *substr, const char *replacement) {
  char *tok = NULL;
  char *newstr = NULL;
  char *oldstr = NULL;
  char *head = NULL;

  /* if either substr or replacement is NULL, duplicate string a let caller handle it */
  if (substr == NULL || replacement == NULL)
    return strdup(string);
  newstr = strdup(string);
  head = newstr;
  if (head) {
    while ((tok = strstr(head, substr))) {
      oldstr = newstr;
      newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1);
      /*failed to alloc mem, free old string and return NULL */
      if (newstr == NULL) {
        free(oldstr);
        return NULL;
      }
      memcpy(newstr, oldstr, tok - oldstr);
      memcpy(newstr + (tok - oldstr), replacement, strlen(replacement));
      memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr),
             strlen(oldstr) - strlen(substr) - (tok - oldstr));
      memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1);
      /* move back head right after the last replacement */
      head = newstr + (tok - oldstr) + strlen(replacement);
      free(oldstr);
    }
  } else {
    die("failed to allocate memory in str_replace.");
  }
  return newstr;
}

/* from http://burtleburtle.net/bob/rand/smallprng.html */

// this is not thread-safe, so we need a mutex on it to use it properly.
// always lock use this when accessing the fp_time_at_last_debug_message

pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;

// typedef uint64_t u8;
typedef struct ranctx {
  uint64_t a;
  uint64_t b;
  uint64_t c;
  uint64_t d;
} ranctx;

static struct ranctx rx;

#define rot(x, k) (((x) << (k)) | ((x) >> (64 - (k))))
uint64_t ranval(ranctx *x) {
  uint64_t e = x->a - rot(x->b, 7);
  x->a = x->b ^ rot(x->c, 13);
  x->b = x->c + rot(x->d, 37);
  x->c = x->d + e;
  x->d = e + x->a;
  return x->d;
}

void raninit(ranctx *x, uint64_t seed) {
  uint64_t i;
  x->a = 0xf1ea5eed, x->b = x->c = x->d = seed;
  for (i = 0; i < 20; ++i) {
    (void)ranval(x);
  }
}

void r64init(uint64_t seed) { raninit(&rx, seed); }

uint64_t r64u() { return (ranval(&rx)); }

int64_t r64i() { return (ranval(&rx) >> 1); }

uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them
  // this is to avoid possible aliasing violations
  uint32_t holder;
  memcpy(&holder, p, sizeof(holder));
  return ntohl(holder);
}

uint16_t nctohs(const uint8_t *p) { // read 2 characters from *p and do ntohs on them
  // this is to avoid possible aliasing violations
  uint16_t holder;
  memcpy(&holder, p, sizeof(holder));
  return ntohs(holder);
}

pthread_mutex_t barrier_mutex = PTHREAD_MUTEX_INITIALIZER;

void memory_barrier() {
  pthread_mutex_lock(&barrier_mutex);
  pthread_mutex_unlock(&barrier_mutex);
}

void sps_nanosleep(const time_t sec, const long nanosec) {
  struct timespec req, rem;
  int result;
  req.tv_sec = sec;
  req.tv_nsec = nanosec;
  do {
    result = nanosleep(&req, &rem);
    rem = req;
  } while ((result == -1) && (errno == EINTR));
  if (result == -1)
    debug(1, "Error in sps_nanosleep of %d sec and %ld nanoseconds: %d.", sec, nanosec, errno);
}

// 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 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();
  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) {

  // this is not pthread_cancellation safe because is contains a cancellation point
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  int time_to_wait = dally_time;
  int r = pthread_mutex_trylock(mutex);
  while ((r == EBUSY) && (time_to_wait > 0)) {
    int st = time_to_wait;
    if (st > 1000)
      st = 1000;
    sps_nanosleep(0, st * 1000); // this contains a cancellation point
    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

int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname,
                      const char *filename, const int line, int debuglevel) {
  if ((debuglevel > debuglev) || (debuglevel == 0))
    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);
  if (result == ETIMEDOUT) {
    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);
  }
  pthread_setcancelstate(oldState, NULL);
  return result;
}

int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename,
                        const int line, int debuglevel) {
  if ((debuglevel > debuglev) || (debuglevel == 0))
    return pthread_mutex_unlock(mutex);
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
  char dstring[1000];
  char errstr[512];
  memset(dstring, 0, sizeof(dstring));
  snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
  debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring);
  int r = pthread_mutex_unlock(mutex);
  if ((debuglevel != 0) && (r != 0))
    debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r,
          strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring);
  pthread_setcancelstate(oldState, NULL);
  return r;
}

void malloc_cleanup(void *arg) {
  // debug(1, "malloc cleanup called.");
  free(arg);
  arg = NULL;
}

void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }

char *get_version_string() {
  char *version_string = malloc(1024);
  if (version_string) {
    strcpy(version_string, PACKAGE_VERSION);

#ifdef CONFIG_APPLE_ALAC
    strcat(version_string, "-alac");
#endif
#ifdef CONFIG_LIBDAEMON
    strcat(version_string, "-libdaemon");
#endif
#ifdef CONFIG_MBEDTLS
    strcat(version_string, "-mbedTLS");
#endif
#ifdef CONFIG_POLARSSL
    strcat(version_string, "-PolarSSL");
#endif
#ifdef CONFIG_OPENSSL
    strcat(version_string, "-OpenSSL");
#endif
#ifdef CONFIG_TINYSVCMDNS
    strcat(version_string, "-tinysvcmdns");
#endif
#ifdef CONFIG_AVAHI
    strcat(version_string, "-Avahi");
#endif
#ifdef CONFIG_DNS_SD
    strcat(version_string, "-dns_sd");
#endif
#ifdef CONFIG_EXTERNAL_MDNS
    strcat(version_string, "-external_mdns");
#endif
#ifdef CONFIG_ALSA
    strcat(version_string, "-ALSA");
#endif
#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_SOUNDIO
    strcat(version_string, "-soundio");
#endif
#ifdef CONFIG_DUMMY
    strcat(version_string, "-dummy");
#endif
#ifdef CONFIG_STDOUT
    strcat(version_string, "-stdout");
#endif
#ifdef CONFIG_PIPE
    strcat(version_string, "-pipe");
#endif
#ifdef CONFIG_SOXR
    strcat(version_string, "-soxr");
#endif
#ifdef CONFIG_CONVOLUTION
    strcat(version_string, "-convolution");
#endif
#ifdef CONFIG_METADATA
    strcat(version_string, "-metadata");
#endif
#ifdef CONFIG_MQTT
    strcat(version_string, "-mqtt");
#endif
#ifdef CONFIG_DBUS_INTERFACE
    strcat(version_string, "-dbus");
#endif
#ifdef CONFIG_MPRIS_INTERFACE
    strcat(version_string, "-mpris");
#endif
    strcat(version_string, "-sysconfdir:");
    strcat(version_string, SYSCONFDIR);
  }
  return version_string;
}

int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
                             int with_dither, int64_t random_number_in) {
  // return the last random number used
  // assuming the buffer has been assigned

  // add a TPDF dither -- see
  // http://educypedia.karadimov.info/library/DitherExplained.pdf
  // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25

  // I think, for a 32 --> 16 bits, the range of
  // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from
  // -32768 to +32767

  // Actually, what would be generated here is from -65535 to 65535, i.e. one less on the limits.

  // See the original paper at
  // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf
  // by Lipshitz, Wannamaker and Vanderkooy, 1992.

  int64_t dither_mask = 0;
  switch (format) {
  case SPS_FORMAT_S32:
  case SPS_FORMAT_S32_LE:
  case SPS_FORMAT_S32_BE:
    dither_mask = (int64_t)1 << (64 - 32);
    break;
  case SPS_FORMAT_S24:
  case SPS_FORMAT_S24_LE:
  case SPS_FORMAT_S24_BE:
  case SPS_FORMAT_S24_3LE:
  case SPS_FORMAT_S24_3BE:
    dither_mask = (int64_t)1 << (64 - 24);
    break;
  case SPS_FORMAT_S16:
  case SPS_FORMAT_S16_LE:
  case SPS_FORMAT_S16_BE:
    dither_mask = (int64_t)1 << (64 - 16);
    break;
  case SPS_FORMAT_S8:
  case SPS_FORMAT_U8:
    dither_mask = (int64_t)1 << (64 - 8);
    break;
  case SPS_FORMAT_UNKNOWN:
    die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask.");
    break;
  case SPS_FORMAT_AUTO:
    die("Unexpected SPS_FORMAT_AUTO while calculating dither mask.");
    break;
  case SPS_FORMAT_INVALID:
    die("Unexpected SPS_FORMAT_INVALID while calculating dither mask.");
    break;
  }
  dither_mask -= 1;

  int64_t previous_random_number = random_number_in;
  char *p = outp;
  size_t sample_number;
  r64_lock; // the random number generator is not thread safe, so we need to lock it while using it
  for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) {

    int64_t hyper_sample = 0;
    int64_t r = r64i();

    int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);

    // add dither if permitted -- no need to check for clipping, as the sample is, uh, zero

    if (with_dither != 0)
      hyper_sample += tpdf;

    // move the result to the desired position in the int64_t
    char *op = p;
    int sample_length; // this is the length of the sample

    switch (format) {
    case SPS_FORMAT_S32:
      hyper_sample >>= (64 - 32);
      *(int32_t *)op = hyper_sample;
      sample_length = 4;
      break;
    case SPS_FORMAT_S32_LE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 32));      // 32 bits, ls byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 32 + 8));  // 32 bits, less significant middle byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 32 + 16)); // 32 bits, more significant middle byte
      *op = (uint8_t)(hyper_sample >> (64 - 32 + 24));   // 32 bits, ms byte
      sample_length = 4;
      break;
    case SPS_FORMAT_S32_BE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 32 + 24)); // 32 bits, ms byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 32 + 16)); // 32 bits, more significant middle byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 32 + 8));  // 32 bits, less significant middle byte
      *op = (uint8_t)(hyper_sample >> (64 - 32));        // 32 bits, ls byte
      sample_length = 4;
      break;
    case SPS_FORMAT_S24_3LE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 24));     // 24 bits, ls byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 8)); // 24 bits, middle byte
      *op = (uint8_t)(hyper_sample >> (64 - 24 + 16));  // 24 bits, ms byte
      sample_length = 3;
      break;
    case SPS_FORMAT_S24_3BE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 16)); // 24 bits, ms byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 8));  // 24 bits, middle byte
      *op = (uint8_t)(hyper_sample >> (64 - 24));        // 24 bits, ls byte
      sample_length = 3;
      break;
    case SPS_FORMAT_S24:
      hyper_sample >>= (64 - 24);
      *(int32_t *)op = hyper_sample;
      sample_length = 4;
      break;
    case SPS_FORMAT_S24_LE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 24));      // 24 bits, ls byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 8));  // 24 bits, middle byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 16)); // 24 bits, ms byte
      *op = 0;
      sample_length = 4;
      break;
    case SPS_FORMAT_S24_BE:
      *op++ = 0;
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 16)); // 24 bits, ms byte
      *op++ = (uint8_t)(hyper_sample >> (64 - 24 + 8));  // 24 bits, middle byte
      *op = (uint8_t)(hyper_sample >> (64 - 24));        // 24 bits, ls byte
      sample_length = 4;
      break;
    case SPS_FORMAT_S16_LE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 16));
      *op++ = (uint8_t)(hyper_sample >> (64 - 16 + 8)); // 16 bits, ms byte
      sample_length = 2;
      break;
    case SPS_FORMAT_S16_BE:
      *op++ = (uint8_t)(hyper_sample >> (64 - 16 + 8)); // 16 bits, ms byte
      *op = (uint8_t)(hyper_sample >> (64 - 16));
      sample_length = 2;
      break;
    case SPS_FORMAT_S16:
      *(int16_t *)op = (int16_t)(hyper_sample >> (64 - 16));
      sample_length = 2;
      break;
    case SPS_FORMAT_S8:
      *op = (int8_t)(hyper_sample >> (64 - 8));
      sample_length = 1;
      break;
    case SPS_FORMAT_U8:
      *op = 128 + (uint8_t)(hyper_sample >> (64 - 8));
      sample_length = 1;
      break;
    default:
      sample_length = 0; // stop a compiler warning
      die("Unexpected SPS_FORMAT_* with index %d while outputting silence", format);
    }
    p += sample_length;
    previous_random_number = r;
  }
  r64_unlock;
  return previous_random_number;
}

// This will check the incoming string "s" of length "len" with the existing NUL-terminated string
// "str" and update "flag" accordingly.

// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings
// are stored.

// If the strings are different, the str is free'd and replaced by a pointer
// to a newly strdup'd string and the flag is set
// If they are the same, the flag is cleared

int string_update_with_size(char **str, int *flag, char *s, size_t len) {
  if (*str) {
    if ((s) && (len)) {
      if ((len != strlen(*str)) || (strncmp(*str, s, len) != 0)) {
        free(*str);
        //*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
        char *p = malloc(len + 1);
        memcpy(p, s, len);
        p[len] = '\0';
        *str = p;
        *flag = 1;
      } else {
        *flag = 0;
      }
    } else {
      // old string is non-NULL, new string is NULL or length 0
      free(*str);
      *str = NULL;
      *flag = 1;
    }
  } else { // old string is NULL
    if ((s) && (len)) {
      //*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
      char *p = malloc(len + 1);
      memcpy(p, s, len);
      p[len] = '\0';
      *str = p;
      *flag = 1;
    } else {
      // old string is NULL and new string is NULL or length 0
      *flag = 0; // so no change
    }
  }
  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;
}
