This squashed commit comprises a long series of improvements, bug fixes, optimizations, and new feature additions:

- 48,000 frames per second operation,
 - 48k lossless stereo, 5.1 and 7.1 surround sound
 - Multichannel and multi rate operation on ALSA, PipeWire, PulseAudio, FreeBSD, stdout and unix pipe output backends.

Automatic, flexible and controllable output format (rate, sample format and channel count) selection.

Full FFmpeg integration to support transcoding, resampling, and new audio formats.

Better operation on lower powered devices down to e.g. Raspberry Pi B.

Reduced Docker image sizes with a slimmed-down FFmpeg library.

Enhanced timestamp handling for better synchronization.

Improved the sync error calculation.

A new "vernier" resampling and interpolation method for low-power CPUs.

Bug fixes and minor enhancements.

Note -- there are many breaking changes from previous versions of Shairport Sync!
This commit is contained in:
Mike Brady
2025-02-23 13:31:17 +00:00
parent 588e6a5b7b
commit 60143518c2
110 changed files with 12416 additions and 7759 deletions
+1 -1
View File
@@ -214,7 +214,7 @@ struct pair_definition
int (*pair_remove)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
int (*pair_list)(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len, const char *dynamic_salt_suffix);
void (*pair_cipher_free)(struct pair_cipher_context *cctx);
ssize_t (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);
+2 -2
View File
@@ -687,12 +687,12 @@ pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx
}
struct pair_cipher_context *
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len)
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len, const char *dynamic_salt_suffix)
{
if (!pair[type]->pair_cipher_new)
return NULL;
return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len);
return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len, dynamic_salt_suffix);
}
void
+1 -1
View File
@@ -204,7 +204,7 @@ pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_
* create a ciphering context.
*/
struct pair_cipher_context *
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len, const char *dynamic_salt_suffix);
void
pair_cipher_free(struct pair_cipher_context *cctx);
+82 -34
View File
@@ -75,6 +75,8 @@ enum pair_keys
PAIR_CONTROL_READ,
PAIR_EVENTS_WRITE,
PAIR_EVENTS_READ,
PAIR_DATA_WRITE,
PAIR_DATA_READ,
};
struct pair_keys_map
@@ -110,6 +112,14 @@ static struct pair_keys_map pair_keys_map[] =
// Encryption/decryption of event channel
{ 0, "Events-Salt", "Events-Write-Encryption-Key", "" },
{ 0, "Events-Salt", "Events-Read-Encryption-Key", "" },
// Encryption/decryption of data channel
// The salt is dynamic -- the 64-bit `seed` provided during SETUP, treated as an unsigned unmber, is appended:
// e.g. DataStream-Salt15014746994705656022.
// Sincere thanks to Pierre Stahl, (latex: St\aahl) https://github.com/postlund.
// Please see https://pyatv.dev/documentation/protocols/#remote-control, AirPlay 2 / Remote Control / Data Channel / Encryption
{ 0, "DataStream-Salt", "DataStream-Output-Encryption-Key", "" },
{ 0, "DataStream-Salt", "DataStream-Input-Encryption-Key", "" },
};
enum pair_method {
@@ -857,7 +867,7 @@ message_process(const uint8_t *data, size_t data_len, const char **errmsg)
else if (error->value[0] == TLVError_MaxPeers)
*errmsg = "Max peers trying to connect to device\n";
else if (error->value[0] == TLVError_MaxTries)
*errmsg = "Max pairing attemps reached\n";
*errmsg = "Max pairing attempts reached\n";
else if (error->value[0] == TLVError_Unavailable)
*errmsg = "Device is unuavailble at this time\n";
else
@@ -877,9 +887,11 @@ message_process(const uint8_t *data, size_t data_len, const char **errmsg)
hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk);
hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len);
add an explicit salt value to allow for a unique salt to be given at initialisation
*/
static int
hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key)
hkdf_extract_expand_with_salt_and_info(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, const char *salt, const char *info)
{
#ifdef CONFIG_OPENSSL
#include <openssl/kdf.h>
@@ -893,11 +905,11 @@ hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm
goto error;
if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()) <= 0)
goto error;
if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) <= 0)
if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, strlen(salt)) <= 0)
goto error;
if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0)
goto error;
if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)) <= 0)
if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, strlen(info)) <= 0)
goto error;
if (EVP_PKEY_derive(pctx, okm, &okm_len) <= 0)
goto error;
@@ -916,7 +928,7 @@ hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm
return -1; // Below calculation not valid if output is larger than hash size
if (gcry_md_open(&hmac_handle, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR)
return -1;
if (gcry_md_setkey(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) != GPG_ERR_NO_ERROR)
if (gcry_md_setkey(hmac_handle, salt, strlen(salt)) != GPG_ERR_NO_ERROR)
goto error;
gcry_md_write(hmac_handle, ikm, ikm_len);
memcpy(prk, gcry_md_read(hmac_handle, 0), sizeof(prk));
@@ -925,7 +937,7 @@ hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm
if (gcry_md_setkey(hmac_handle, prk, sizeof(prk)) != GPG_ERR_NO_ERROR)
goto error;
gcry_md_write(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info));
gcry_md_write(hmac_handle, info, strlen(info));
gcry_md_putc(hmac_handle, 1);
memcpy(okm, gcry_md_read(hmac_handle, 0), okm_len);
@@ -941,6 +953,17 @@ hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm
#endif
}
/* Executes SHA512 RFC 5869 extract + expand, writing a derived key to okm
hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk);
hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len);
*/
static int
hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key) {
// pass in the salt that is in the pair_key_maps table
return hkdf_extract_expand_with_salt_and_info(okm, okm_len, ikm, ikm_len, pair_keys_map[pair_key].salt, pair_keys_map[pair_key].info);
}
static int
encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH])
{
@@ -2890,47 +2913,22 @@ cipher_free(struct pair_cipher_context *cctx)
}
static struct pair_cipher_context *
cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len)
cipher_new_with_salt_and_info(struct pair_definition *type, const uint8_t *shared_secret, size_t shared_secret_len, const char *write_salt, const char *write_info, const char *read_salt, const char *read_info)
{
struct pair_cipher_context *cctx;
enum pair_keys write_key;
enum pair_keys read_key;
int ret;
// Note that events is opposite, probably because it is a reverse connection
switch (channel)
{
case 0:
write_key = PAIR_CONTROL_WRITE;
read_key = PAIR_CONTROL_READ;
break;
case 1:
write_key = PAIR_EVENTS_READ;
read_key = PAIR_EVENTS_WRITE;
break;
case 2:
write_key = PAIR_CONTROL_READ;
read_key = PAIR_CONTROL_WRITE;
break;
case 3:
write_key = PAIR_EVENTS_WRITE;
read_key = PAIR_EVENTS_READ;
break;
default:
return NULL;
}
cctx = calloc(1, sizeof(struct pair_cipher_context));
if (!cctx)
goto error;
cctx->type = type;
ret = hkdf_extract_expand(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_key);
ret = hkdf_extract_expand_with_salt_and_info(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_salt, write_info);
if (ret < 0)
goto error;
ret = hkdf_extract_expand(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_key);
ret = hkdf_extract_expand_with_salt_and_info(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_salt, read_info);
if (ret < 0)
goto error;
@@ -2941,6 +2939,56 @@ cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secr
return NULL;
}
static struct pair_cipher_context *
cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len, const char *dynamic_salt_suffix)
{
enum pair_keys write_key;
enum pair_keys read_key;
// Note that events is opposite, probably because it is a reverse connection
switch (channel)
{
case 0: // control
write_key = PAIR_CONTROL_WRITE;
read_key = PAIR_CONTROL_READ;
break;
case 1: // events
write_key = PAIR_EVENTS_READ;
read_key = PAIR_EVENTS_WRITE;
break;
case 2: // data
write_key = PAIR_DATA_WRITE;
read_key = PAIR_DATA_READ;
break;
case 3: // control
write_key = PAIR_CONTROL_READ;
read_key = PAIR_CONTROL_WRITE;
break;
case 4: // events
write_key = PAIR_EVENTS_WRITE;
read_key = PAIR_EVENTS_READ;
break;
case 5: // data
write_key = PAIR_DATA_READ;
read_key = PAIR_DATA_WRITE;
break;
default:
return NULL;
}
//if ((dynamic_salt_suffix == NULL) || (dynamic_salt_suffix[0] == '\0')) {
// return cipher_new_with_salt_and_info(type, shared_secret, shared_secret_len, pair_keys_map[write_key].salt, pair_keys_map[write_key].info, pair_keys_map[read_key].salt, pair_keys_map[read_key].info);
//} else {
char write_salt[256] = "";
char read_salt[256] = "";
snprintf(write_salt, sizeof(write_salt), "%s%s", pair_keys_map[write_key].salt, dynamic_salt_suffix);
//printf("channel %d, dynamic_salt_suffix: \"%s\", write_salt: \"%s\"\n", channel, dynamic_salt_suffix, write_salt);
snprintf(read_salt, sizeof(read_salt), "%s%s", pair_keys_map[read_key].salt, dynamic_salt_suffix);
//printf("channel %d, dynamic_salt_suffix: \"%s\", read_salt: \"%s\"\n", channel, dynamic_salt_suffix, read_salt);
return cipher_new_with_salt_and_info(type, shared_secret, shared_secret_len, write_salt, pair_keys_map[write_key].info, read_salt, pair_keys_map[read_key].info);
//}
}
static ssize_t
encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx)
{