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