Files
shairport-sync/common.c
T
Mike Brady f30dd7aee6 Enhancements
Enable the AirPlay 2 build to operate an optional classic-AirPlay-only service or to gracefully degrade to classic AirPlay, as follows:
  1. Add a new command-line option '-—service-type=<type>' and an equivalent configuration entry 'service_type = "<type>"' in the 'general' section of the configuration file,
     where <type> can be "auto", "classic" or "airplay2":
      1. "auto" (default) means that the service will be AirPlay 2 if NQPTP is running. If NQPTP is not running, classic AirPlay service will be provided instead, and in that case,
      "(Classic)" will be appended to the default AirPlay service name visible to AirPlay clients like Apple Music, for example "RaspberryPi3B (Classic)".
      2. "classic" means the service will be classic AirPlay (aka AirPlay 1).
      3. "airplay2" means the service will be the modern AirPlay 2. In this case, as distinct from "auto", if NQPTP is not running, Shairport Sync will log an error and terminate.
  2. In the systemd service file, NQPTP is now a "Want" rather than a "Require". If it's present, then it will be launched before Shairport Sync. If it's absent, Shairport Sync will launch anyway.
  3. Improve the delivery of input format changes and emit 'sdsc' metadata when changes occur.
  4. Emit new format information in the log if statistics is enabled.

Docker Changes
  1. Support for linux/arm/v6 has been dropped, as Docker is no longer supported.
  2. NQPTP is not started in the AirPlay 2 Docker image if '--service-type=classic' or '--service-type=airplay1' is in the command line options at the end of the docker run command.
     The purpose is to ensure that ports 319 and 320 are left alone when the AirPlay 2 image is set to provide Classic service only.
     Note that setting the configuration file 'service_type' to 'classic' will not prevent NQPTP from starting up -- you must use the command line option.
  3. A new "dev" target has been added. It is a large image containing the custom-built FFmpeg library, NQPTP, Avahi and D-Bus along with the Shairport Sync source and
     all necessary development tools. When started, Avahi, D-Bus and NQPTP are all installed and running. The bash shell has also been added and is entered.

Stability Improvements
  Reorganise session preemption to fully terminate the existing session before starting a new one.
  Don't delay closing the event port to wait for it to be closed at the client end.
  Add a safe_socket_close() function to ensure sockets are fully closed. Use -1 to designate closed rather than 0, to prevent attempts to reclose sockets, causing mayhem.
  Re-order FFmpeg decommissioning during teardown.
2026-05-18 09:57:12 +01:00

2735 lines
90 KiB
C

/*
* 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--2025
* 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 "utilities/network_utilities.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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.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_CONVOLUTION
#include <ctype.h>
#include <sndfile.h>
#endif
#ifdef CONFIG_OPENSSL
#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
#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>
#if MBEDTLS_VERSION_MAJOR == 3
#define MBEDTLS_PRIVATE_V3_ONLY(_q) MBEDTLS_PRIVATE(_q)
#else
#define MBEDTLS_PRIVATE_V3_ONLY(_q) _q
#endif
#endif
#ifdef CONFIG_LIBDAEMON
#include <libdaemon/dlog.h>
#else
#include <syslog.h>
#endif
#ifdef CONFIG_ALSA
void set_alsa_out_dev(char *);
#endif
#ifdef CONFIG_AIRPLAY_2
#include "nqptp-shm-structures.h"
#endif
config_t config_file_stuff;
int type_of_exit_cleanup;
uint64_t minimum_dac_queue_size;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
unsigned int sps_format_sample_size_array[] = {
0, // unknown
1, 1, // S8, U8
2, 2, // S16_LE, S16_BE,
4, 4, // S24_LE, S24_BE,
3, 3, // S24_3LE, S24_3BE,
4, 4, // S32_LE, S32_BE,
2, 4, 4, // S16, S24, S32
0, 0 // Auto, Invalid
};
unsigned int sps_format_sample_size(sps_format_t format) {
unsigned int response = 0;
if (format <= SPS_FORMAT_AUTO)
response = sps_format_sample_size_array[format];
return response;
}
const char *sps_format_description_string_array[] = {
"unknown", "S8", "U8", "S16_LE", "S16_BE", "S24_LE", "S24_BE", "S24_3LE",
"S24_3BE", "S32_LE", "S32_BE", "S16", "S24", "S32", "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];
}
unsigned int sps_rate_actual_rate(sps_rate_t rate) {
unsigned int response = 0;
switch (rate) {
case SPS_RATE_5512:
response = 5512;
break;
case SPS_RATE_8000:
response = 8000;
break;
case SPS_RATE_11025:
response = 11025;
break;
case SPS_RATE_16000:
response = 16000;
break;
case SPS_RATE_22050:
response = 22050;
break;
case SPS_RATE_32000:
response = 32000;
break;
case SPS_RATE_44100:
response = 44100;
break;
case SPS_RATE_48000:
response = 48000;
break;
case SPS_RATE_64000:
response = 64000;
break;
case SPS_RATE_88200:
response = 88200;
break;
case SPS_RATE_96000:
response = 96000;
break;
case SPS_RATE_176400:
response = 176400;
break;
case SPS_RATE_192000:
response = 192000;
break;
case SPS_RATE_352800:
response = 352800;
break;
case SPS_RATE_384000:
response = 384000;
break;
default:
debug(1, "unrecognised SPS_RATE_: %u.", rate);
break;
}
return response;
}
char sfd[32];
const char *short_format_description(int32_t encoded_format) {
if (encoded_format < 0)
snprintf(sfd, sizeof(sfd) - 1, "error %d", encoded_format);
else
snprintf(
sfd, sizeof(sfd) - 1, "%u/%s/%u", RATE_FROM_ENCODED_FORMAT(encoded_format),
sps_format_description_string((sps_format_t)(FORMAT_FROM_ENCODED_FORMAT(encoded_format))),
CHANNELS_FROM_ENCODED_FORMAT(encoded_format));
return (const char *)sfd;
}
// 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[16384];
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[16384];
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[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 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;
sigset_t pselect_sigset;
// note -- don't use this to shutdown from dbus -- see its own code in dbus-service.c
void sps_shutdown(type_of_exit_type shutdown_type) { // TOE_normal, TOE_emergency
type_of_exit_cleanup = shutdown_type;
if (type_of_exit_cleanup == TOE_emergency) {
debug(1, "emergency shutdown requested");
exit(EXIT_FAILURE);
} else {
debug(1, "normal shutdown requested");
exit(EXIT_SUCCESS);
}
}
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;
}
// 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;
safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((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;
safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((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) {
safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((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; }
void getErrorText(char *destinationString, size_t destinationStringLength) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
strerror_r(errno, destinationString, destinationStringLength);
#pragma GCC diagnostic pop
}
// 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);
}
// including a simple base64 encoder to minimise malloc/free activity
// From Stack Overflow, with thanks:
// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
// minor mods to make independent of C99.
// more significant changes make it not malloc memory
// needs to initialise the encoding table first
// add _so to end of name to avoid confusion with polarssl's implementation
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
static size_t mod_table[] = {0, 2, 1};
// pass in a pointer to the data, its length, a pointer to the output buffer and
// a pointer to an int
// containing its maximum length
// the actual length will be returned.
char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data,
size_t *output_length) {
size_t calculated_output_length = 4 * ((input_length + 2) / 3);
if (calculated_output_length > *output_length)
return (NULL);
*output_length = calculated_output_length;
size_t i, j;
for (i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
}
for (i = 0; i < mod_table[input_length % 3]; i++)
encoded_data[*output_length - 1 - i] = '=';
return encoded_data;
}
// with thanks!
//
#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 %ld.", 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);
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 %zu.", 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));
}
*outlen = ol;
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);
#if MBEDTLS_VERSION_MAJOR == 3
rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key),
NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg);
#else
rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key),
NULL, 0);
#endif
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->MBEDTLS_PRIVATE_V3_ONLY(len));
#if MBEDTLS_VERSION_MAJOR == 3
rc = mbedtls_pk_sign(&pkctx, MBEDTLS_MD_NONE, input, inlen, outbuf, mbedtls_pk_get_len(&pkctx),
&olen, mbedtls_ctr_drbg_random, &ctr_drbg);
*outlen = olen;
#else
rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
inlen, input, outbuf);
*outlen = trsa->len;
#endif
if (rc != 0)
debug(1, "mbedtls_pk_encrypt error %d.", rc);
break;
case RSA_MODE_KEY:
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
outbuf = malloc(trsa->MBEDTLS_PRIVATE_V3_ONLY(len));
#if MBEDTLS_VERSION_MAJOR == 3
rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, &olen, input, outbuf,
trsa->MBEDTLS_PRIVATE_V3_ONLY(len));
#else
rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
&olen, input, outbuf, trsa->len);
#endif
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_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) {
int response = CONFIG_FALSE;
config_setting_t *s = config_lookup(cfg, path);
if (s != NULL) {
// the setting exists, but might not be a string
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
if (value != NULL) {
*value = config_setting_get_string(s);
response = CONFIG_TRUE;
// the string might be empty...
if ((*value == NULL) || (*value[0] == 0)) {
warn("The \"%s\" parameter is an empty string and has been ignored.", path);
response = CONFIG_FALSE;
}
}
} else {
warn("the \"%s\" parameter is not a string, as required, and has been ignored.", path);
}
}
return response;
}
int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
const char *str = NULL;
int response = CONFIG_FALSE;
config_setting_t *s = config_lookup(cfg, where);
if (s != NULL) {
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
str = config_setting_get_string(s);
if (strcasecmp(str, "no") == 0) {
(*dst) = 0;
response = CONFIG_TRUE;
} else if (strcasecmp(str, "yes") == 0) {
(*dst) = 1;
response = CONFIG_TRUE;
} else {
die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"",
where, str);
return 0;
}
} else {
warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, "
"and has been ignored.",
where);
}
}
return response;
}
// remember to free the returned array of strings.
// you don't need to free the strings themselves -- they belong to libconfig.
unsigned int config_get_string_settings_as_string_array(config_setting_t *setting,
const char ***result) {
unsigned int count = 0;
int error = 0;
*result = NULL;
const char **arr = NULL;
if (setting != NULL) { // definitely a setting
const char *str = config_setting_get_string(setting);
if (str != NULL) { // definitely a string
arr = malloc(sizeof(const char *));
arr[0] = str;
count = 1;
} else { // it might be a list, an array or a group
count = config_setting_length(setting);
if (count != 0) {
arr = malloc(sizeof(const char *) * count);
unsigned int i;
for (i = 0; i < count; i++) {
config_setting_t *item = config_setting_get_elem(setting, i);
if (config_setting_type(item) == CONFIG_TYPE_STRING)
arr[i] = config_setting_get_string(item);
else
error = i + 1;
}
} else {
error = 1;
}
}
}
if (error != 0) {
if (arr != NULL) {
free(arr);
}
count = -error; // signify an error
} else {
*result = arr;
}
return count;
}
// remember to free the returned array of ints.
unsigned int config_get_int_settings_as_int_array(config_setting_t *setting, int **result) {
int error = 0;
unsigned int count = 0;
*result = NULL;
int *arr = NULL;
if (setting != NULL) { // definitely a setting there
if (config_setting_type(setting) == CONFIG_TYPE_INT) {
arr = malloc(sizeof(int));
arr[0] = config_setting_get_int(setting);
count = 1;
} else if (config_setting_is_aggregate(setting) == CONFIG_TRUE) {
count = config_setting_length(setting);
if (count != 0) {
arr = malloc(sizeof(int) * count);
unsigned int i;
for (i = 0; i < count; i++) {
config_setting_t *item = config_setting_get_elem(setting, i);
if (config_setting_type(item) == CONFIG_TYPE_INT)
arr[i] = config_setting_get_int(item);
else
error = i + 1;
}
}
} else {
error = 1; // subtract 1 from the error number to get the element number
}
}
if (error != 0) {
if (arr != NULL) {
free(arr);
}
count = -error; // signify an error
} else {
*result = arr;
}
return count;
}
// Look for the item in the setting which could be either a string or an array or list or group of
// strings. Result: 0 means there is a setting but no match, 1 means there's no setting, 2 means
// "auto" was found, 3 means a match.
int check_string_or_list_setting(config_setting_t *setting, const char *item) {
int result = 1; // means there is no setting at all (so the caller should implement the default)
if (setting != NULL) { // definitely a setting
const char *str = config_setting_get_string(setting);
debug(3, "check \"%s\" against \"%s\"", str, item);
if (str != NULL) { // definitely a string
if (strcasecmp(str, item) == 0) {
result = 3; // an exact match
} else if (strcasecmp(str, "auto") == 0) {
result = 2; // auto
} else {
result = 0; // a string that is not a match
}
} else { // it might be a list, an array or a group
int i = 0;
result = 0; // presume there is no match
// keep looking, even if "auto" has been found, to see if the exact match (preferred) is there
// too.
while (((result == 0) || (result == 2)) && (i < config_setting_length(setting))) {
const char *str2 = config_setting_get_string_elem(setting, i);
if (str2 != NULL) { // definitely a string
if (strcasecmp(str2, "auto") == 0) {
result = 2; // auto
} else if (strcasecmp(str2, item) == 0) {
result = 3; // an exact match
}
}
i++; // will point to 1 past the found item or last item.
}
}
}
return result;
}
// Look for the item in the setting which could be either an int or an array or list or group of
// ints. Result: 0 means there is a setting but no match, 1 means there's no setting, 2 means "auto"
// was found, 3 means a match.
int check_int_or_list_setting(config_setting_t *setting, const int item) {
int result = 1; // means there is no setting at all
if (setting != NULL) { // definitely a setting
int setting_type = config_setting_type(setting);
if (setting_type == CONFIG_TYPE_STRING) {
if (strcasecmp(config_setting_get_string(setting), "auto") == 0) {
result = 2; // auto
} else {
result = 0; // a string that can not be a match
}
} else if (setting_type == CONFIG_TYPE_INT) {
if (item == config_setting_get_int(setting))
result = 3; // an exact match
else
result = 0; // a setting but not a match
} else { // it might be a list, an array or a group
int i = 0;
result = 0; // presume there is no match (there is a setting)
// keep looking, even if "auto" has been found, to see if the exact match (preferred) is there
// too.
while (((result == 0) || (result == 2)) && (i < config_setting_length(setting))) {
config_setting_t *sub_setting = config_setting_get_elem(setting, i);
int sub_setting_type = config_setting_type(sub_setting);
if (sub_setting_type == CONFIG_TYPE_STRING) {
if (strcasecmp(config_setting_get_string_elem(sub_setting, i), "auto") == 0) {
result = 2; // auto
}
} else if (sub_setting_type == CONFIG_TYPE_INT) {
if (item == config_setting_get_int(sub_setting))
result = 3; // an exact match
}
i++; // will point to 1 past the found item or last item.
}
}
}
return result;
}
void service_type_to_string(APST_t service_type, char *string_space) {
if (string_space != NULL) {
string_space[0] = '\0';
switch (service_type) {
case APST_auto:
strcpy(string_space, "auto");
break;
case APST_classic:
strcpy(string_space, "classic");
break;
case APST_forced_classic:
strcpy(string_space, "forced_classic");
break;
case APST_airplay2:
strcpy(string_space, "airplay2");
break;
}
}
}
APST_t string_to_service_type(const char *parameter, const char *setting_name) {
APST_t response = APST_auto;
if (parameter != NULL) {
if (strcasecmp(parameter, "auto") == 0) {
response = APST_auto;
} else if (strcasecmp(parameter, "classic") == 0) {
response = APST_classic;
} else if (strcasecmp(parameter, "airplay1") == 0) {
response = APST_classic;
} else if (strcasecmp(parameter, "airplay2") == 0) {
response = APST_airplay2;
} else {
warn("The %s \"%s\" was ignored. It must be \"auto\", \"classic\" or \"airplay2\". (You can "
"use \"airplay1\" instead of \"classic\".)",
setting_name, parameter);
}
#ifndef CONFIG_AIRPLAY_2
if (response == APST_airplay2) {
warn("This version of Shairport Sync supports does not support AirPlay 2. The %s \"%s\" "
"setting has been ignored.",
setting_name, parameter);
response = APST_auto; // reset to default
}
#endif
}
/*
char service_type_string[32];
service_type_to_string(response, service_type_string);
debug(1, "config.service_type read from %s is: \"%s\".", setting_name, service_type_string);
*/
return response;
}
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) {
safe_socket_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);
safe_socket_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;
safe_socket_close(&pipes[1]);
len = read(pipes[0], buffer, 255);
safe_socket_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[2048];
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;
}
// 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)) {
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,
"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;
}
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
// 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]) {
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);
if (tvol < vol_setting)
vol_setting = tvol;
}
}
vol_setting += max_db;
} else if (vol != -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 {
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_monotonic_time_in_ns() {
uint64_t time_now_ns;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
struct timespec tn;
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;
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.
// 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);
}
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
time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
#endif
return time_now_ns;
}
#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;
#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;
time_now_ns = tnnsec + tnjnsec;
#endif
#ifdef COMPILE_FOR_OSX
uint64_t time_now_mach;
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.
// 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.
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
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];
getErrorText((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];
getErrorText((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://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);
}
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() {
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 %" PRIdMAX " sec and %ld nanoseconds: %d.", (intmax_t)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) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct timespec timeoutTime;
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);
pthread_setcancelstate(oldState, NULL);
return r;
}
#endif
#ifdef COMPILE_FOR_OSX
*/
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time) {
// 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;
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);
}
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 > debug_level()) || (debuglevel == 0))
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
if (debuglevel != 0)
_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);
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;
}
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename,
const int line, int debuglevel) {
if ((debuglevel > debug_level()) || (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)) {
if (strerror_r(r, errstr, sizeof(errstr)) == 0) {
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring);
} else {
debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring);
}
}
pthread_setcancelstate(oldState, NULL);
return r;
}
void malloc_cleanup(void *arg) {
// the address of the malloc variable is passed in case a realloc is done as some time
// debug(1, "malloc cleanup called.");
void **allocation = arg;
void *ref = *allocation;
if (ref != NULL)
free(ref);
}
#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) {
int *p = (int *)arg;
debug(3, "socket_cleanup called for socket: %d.", *p);
safe_socket_close(p);
}
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 rwlock_unlock(void *arg) { pthread_rwlock_unlock((pthread_rwlock_t *)arg); }
void mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_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); }
char *get_version_string() {
char *version_string = malloc(1024);
if (version_string) {
#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
#ifndef CONFIG_AIRPLAY_2
#ifdef CONFIG_FFMPEG
strcat(version_string, "-FFmpeg");
#endif
#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_PULSEAUDIO
strcat(version_string, "-PulseAudio");
#endif
#ifdef CONFIG_PIPEWIRE
strcat(version_string, "-PipeWire");
#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, int with_dither,
int64_t random_number_in, uint32_t encoded_output_format) {
int64_t previous_random_number = random_number_in;
if (encoded_output_format != 0) {
unsigned int channels = CHANNELS_FROM_ENCODED_FORMAT(encoded_output_format);
sps_format_t format = (sps_format_t)FORMAT_FROM_ENCODED_FORMAT(encoded_output_format);
// 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;
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 * channels; 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;
/*
{
// hack to generate low level white noise instead of adding dither
hyper_sample = r;
hyper_sample = hyper_sample / (1 << 8); // keep the sign
}
*/
// 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;
} else {
debug(1, "No output configuration!");
}
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;
}
// 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;
}
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 {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
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 (((ifa->ifa_flags & IFF_UP) != 0) && ((ifa->ifa_flags & IFF_RUNNING) != 0) &&
((ifa->ifa_flags & IFF_LOOPBACK) == 0) && (ifa->ifa_addr != 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);
}
pthread_setcancelstate(oldState, NULL);
// 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;
}
char *bnprintf(char *buffer, ssize_t max_bytes, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
va_list args;
va_start(args, format);
vsnprintf(buffer, max_bytes, format, args);
va_end(args);
pthread_setcancelstate(oldState, NULL);
// debug(1,"bnprintf string is: \"%s\"", buffer);
return buffer;
}
int do_pthread_setname(pthread_t *restrict thread, const char *format, ...) {
#ifdef COMPILE_FOR_OSX
return 0;
#else
// pthread_setname_np/2 not defined in macOS
char actual_name[16];
va_list args;
va_start(args, format);
vsnprintf(actual_name, sizeof(actual_name), format, args);
va_end(args);
return pthread_setname_np(*thread, actual_name);
#endif
}
int named_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg, const char *format, ...) {
char actual_name[16];
va_list args;
va_start(args, format);
vsnprintf(actual_name, sizeof(actual_name), format, args);
va_end(args);
int response = pthread_create(thread, attr, start_routine, arg);
if (response != 0) {
debug(1, "error creating thread \"%s\"", actual_name);
}
#ifndef COMPILE_FOR_OSX
if (response == 0) {
pthread_setname_np(*thread, actual_name);
}
#endif
return response;
}
int named_pthread_create_with_priority(pthread_t *thread, int priority,
void *(*start_routine)(void *), void *arg,
const char *format, ...) {
// if this gets a permissions error, it'll try to create a thread without any special
// priority or scheduling
static int failed_to_set_rt = 0;
struct sched_param param;
pthread_attr_t attr;
int ret = 0;
char actual_name[16];
va_list args;
va_start(args, format);
vsnprintf(actual_name, sizeof(actual_name), format, args);
va_end(args);
/* Initialize pthread attributes (default values) */
ret = pthread_attr_init(&attr);
if (ret == 0) {
/* Set scheduler policy and priority of pthread */
ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
if (ret == 0) {
param.sched_priority = priority;
ret = pthread_attr_setschedparam(&attr, &param);
if (ret == 0) {
/* Use scheduling parameters of attr */
ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (ret != 0) {
debug(1, "pthread setinheritsched failed");
}
} else {
debug(1, "pthread setschedparam failed");
}
} else {
debug(1, "pthread setschedpolicy failed");
}
} else {
debug(1, "init pthread attributes failed");
}
// ret == 0 if creating and setting up the attributes was successful
if (ret == 0) {
ret = pthread_create(thread, &attr, start_routine, arg);
pthread_attr_destroy(&attr);
}
// ret will be non-zero if there was a problem creating the attribute or creating the prioritized
// thread
if (ret != 0) {
ret = pthread_create(thread, NULL, start_routine, arg);
if (failed_to_set_rt == 0) {
inform("Can not set realtime properties of thread \"%s\".", actual_name);
failed_to_set_rt = 1;
}
}
#ifndef COMPILE_FOR_OSX
if (ret == 0) {
pthread_setname_np(*thread, actual_name);
} else {
die("named_pthread_create_with_priority failed with error %d", ret);
}
#endif
return ret;
}
#ifdef CONFIG_CONVOLUTION
/* Parse comma-separated filenames with optional quotes
* Returns array of ir_file_info_t structs (caller must free both array and filenames)
* count is set to number of filenames found
* Returns NULL on error
*/
ir_file_info_t *parse_ir_filenames(const char *input, unsigned int *file_count) {
if (!input || !file_count)
return NULL;
*file_count = 0;
unsigned int capacity = 10;
ir_file_info_t *files = malloc(capacity * sizeof(ir_file_info_t));
if (!files)
return NULL;
const char *p = input;
while (*p) {
/* Skip whitespace before filename */
while (isspace((unsigned char)*p))
p++;
if (!*p)
break;
/* Check if we need to resize array */
if (*file_count >= capacity) {
capacity *= 2;
ir_file_info_t *temp = realloc(files, capacity * sizeof(ir_file_info_t));
if (!temp) {
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
files = temp;
}
/* Parse one filename */
char quote_char = 0;
char *buffer = NULL;
size_t buf_len = 0;
size_t buf_cap = 64;
if (*p == '"' || *p == '\'') {
/* Quoted filename */
quote_char = *p;
p++;
buffer = malloc(buf_cap);
if (!buffer) {
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
/* Parse quoted string with escape handling */
while (*p && *p != quote_char) {
if (*p == '\\' && *(p + 1)) {
/* Escape sequence */
p++;
if (buf_len >= buf_cap - 1) {
buf_cap *= 2;
char *temp = realloc(buffer, buf_cap);
if (!temp) {
free(buffer);
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
buffer = temp;
}
buffer[buf_len++] = *p++;
} else {
if (buf_len >= buf_cap - 1) {
buf_cap *= 2;
char *temp = realloc(buffer, buf_cap);
if (!temp) {
free(buffer);
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
buffer = temp;
}
buffer[buf_len++] = *p++;
}
}
buffer[buf_len] = '\0';
if (*p == quote_char)
p++; /* Skip closing quote */
files[*file_count].samplerate = 0;
// files[*file_count].evaluation = ev_unchecked;
files[*file_count].filename = buffer;
(*file_count)++;
} else {
/* Unquoted filename - read until comma or end, handle escapes */
buffer = malloc(buf_cap);
if (!buffer) {
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
while (*p && *p != ',') {
if (*p == '\\' && *(p + 1)) {
/* Escape sequence */
p++;
if (buf_len >= buf_cap - 1) {
buf_cap *= 2;
char *temp = realloc(buffer, buf_cap);
if (!temp) {
free(buffer);
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
buffer = temp;
}
buffer[buf_len++] = *p++;
} else {
if (buf_len >= buf_cap - 1) {
buf_cap *= 2;
char *temp = realloc(buffer, buf_cap);
if (!temp) {
free(buffer);
for (unsigned int i = 0; i < *file_count; i++)
free(files[i].filename);
free(files);
return NULL;
}
buffer = temp;
}
buffer[buf_len++] = *p++;
}
}
/* Trim trailing whitespace */
while (buf_len > 0 && isspace((unsigned char)buffer[buf_len - 1])) {
buf_len--;
}
buffer[buf_len] = '\0';
files[*file_count].samplerate = 0;
files[*file_count].channels = 0;
// files[*file_count].evaluation = ev_unchecked;
files[*file_count].filename = buffer;
(*file_count)++;
}
/* Skip comma and whitespace */
while (isspace((unsigned char)*p))
p++;
if (*p == ',') {
p++;
while (isspace((unsigned char)*p))
p++;
}
}
return files;
}
/* Do a quick sanity check on the files -- see if they can be opened as sound files */
void sanity_check_ir_files(const int option_print_level, ir_file_info_t *files,
unsigned int count) {
if (files != NULL) {
debug(option_print_level, "convolution impulse response files: %d found.", count);
for (unsigned int i = 0; i < count; i++) {
SF_INFO sfinfo = {};
// sfinfo.format = 0;
SNDFILE *file = sf_open(files[i].filename, SFM_READ, &sfinfo);
if (file) {
// files[i].evaluation = ev_okay;
files[i].samplerate = sfinfo.samplerate;
files[i].channels = sfinfo.channels;
debug(option_print_level,
"convolution impulse response file \"%s\": %" PRId64
" frames (%.1f seconds), %d channel%s at %d frames per second.",
files[i].filename, sfinfo.frames, (float)sfinfo.frames / sfinfo.samplerate,
sfinfo.channels, sfinfo.channels == 1 ? "" : "s", sfinfo.samplerate);
sf_close(file);
} else {
// files[i].evaluation = ev_invalid;
debug(option_print_level, "convolution impulse response file \"%s\" %s", files[i].filename,
sf_strerror(NULL));
warn("Error accessing the convolution impulse response file \"%s\". %s", files[i].filename,
sf_strerror(NULL));
}
}
} else {
debug(option_print_level, "no convolution impulse response files found.");
}
}
/* Free the array returned by parse_filenames */
void free_ir_filenames(ir_file_info_t *files, unsigned int file_count) {
if (!files)
return;
for (unsigned int i = 0; i < file_count; i++) {
free(files[i].filename);
}
free(files);
}
#endif