If cleanup isn’t finished after one second, exit will occur unconditionally. Use the new exit handler for normal exit, for SIGINT and SIGTERM handling, for abnormal exit after fatal errors, and for handling quit requests from the D-Bus and MPRIS interfaces. When an unfixable error occurs without an installed unfixable error handler, unlock the mutex protecting Shairport Sync’s alsa backend to permit a clean exit. Fix a very low-level mutex-locking error that interfered with debugging. Simplify much mutex lock handling. Remove lots of old and unused code, variables and definitions. Update audio_sndio.c to remove some harmless compiler warnings. Undo adding two seconds for messages to be output before _Exit() on die() to allow messages to be output... Undo making die() exit unconditionally.
2614 lines
86 KiB
C
2614 lines
86 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;
|
|
uint64_t minimum_dac_queue_size;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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, ¶m);
|
|
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
|