Files
shairport-sync/rtsp.c
T
Mike Brady 8201903a4e Add code to try to save your ears by handling a situation where the player volume was set very high some time ago and you have forgotten.
In that situation currenctly, when you play something, it will come out at the last volume set on the device, which will be that very high volume.
This could happen, for example, the morning after a loud party :).

The new code works as follows: If nothing has been played for a period, say a few hours, and the volume is higher than a threshold, new connections that ask
for the current volume setting will be offered the default (low) volume rather than the current (very high) one.
Most players, including the HomePod mini, iOS and iPadOS use this to set their initial volume.
The Mac Music app retains its own setting, and the Mac System Sounds preference sometimes seems to take its setting from the Music app,
but at least you can see the settings they will apply.

Existing connections are unaffected, so this facility should be unobtrusive.
2023-01-18 10:41:50 +00:00

5616 lines
206 KiB
C

/*
* RTSP protocol handler. This file is part of Shairport Sync
* Copyright (c) James Laird 2013
* Modifications associated with audio synchronization, multithreading and
* metadata handling copyright (c) Mike Brady 2014-2023
* 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 <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <inttypes.h>
#include <limits.h>
#include <memory.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "activity_monitor.h"
#include "config.h"
#ifdef CONFIG_OPENSSL
#include <openssl/md5.h>
#endif
#ifdef CONFIG_MBEDTLS
#include <mbedtls/md5.h>
#include <mbedtls/version.h>
#endif
#ifdef CONFIG_POLARSSL
#include <polarssl/md5.h>
#endif
#include "common.h"
#include "player.h"
#include "rtp.h"
#include "rtsp.h"
#ifdef CONFIG_METADATA_HUB
#include "metadata_hub.h"
#endif
#ifdef CONFIG_MQTT
#include "mqtt.h"
#endif
#ifdef AF_INET6
#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN
#else
#define INETx_ADDRSTRLEN INET_ADDRSTRLEN
#endif
#ifdef CONFIG_AIRPLAY_2
#include "pair_ap/pair.h"
#include "plist/plist.h"
#include "plist_xml_strings.h"
#include "ptp-utilities.h"
#endif
#ifdef CONFIG_DBUS_INTERFACE
#include "dbus-service.h"
#endif
#include "mdns.h"
// mDNS advertisement strings
// Create these strings and then keep them updated.
// When necessary, update the mDNS service records, using e.g. Avahi
// from these sources.
char *txt_records[64];
char *secondary_txt_records[64];
char firmware_version[64];
char ap1_featuresString[64];
char pkString[128];
#ifdef CONFIG_AIRPLAY_2
char deviceIdString[64];
char featuresString[64];
char statusflagsString[32];
char piString[128];
char gidString[128];
#endif
#define METADATA_SNDBUF (4 * 1024 * 1024)
enum rtsp_read_request_response {
rtsp_read_request_response_ok,
rtsp_read_request_response_immediate_shutdown_requested,
rtsp_read_request_response_bad_packet,
rtsp_read_request_response_channel_closed,
rtsp_read_request_response_read_error,
rtsp_read_request_response_error
};
rtsp_conn_info *playing_conn;
rtsp_conn_info **conns;
int metadata_running = 0;
// always lock this when accessing the playing conn value
pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER;
// always lock this when accessing the list of connection threads
pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER;
// every time we want to retain or release a reference count, lock it with this
// if a reference count is read as zero, it means the it's being deallocated.
static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
// only one thread is allowed to use the player at once.
// it monitors the request variable (at least when interrupted)
// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
// static int please_shutdown = 0;
// static pthread_t playing_thread = 0;
int RTSP_connection_index = 1;
#ifdef CONFIG_METADATA
typedef struct {
pthread_mutex_t pc_queue_lock;
pthread_cond_t pc_queue_item_added_signal;
pthread_cond_t pc_queue_item_removed_signal;
char *name;
size_t item_size; // number of bytes in each item
uint32_t count; // number of items in the queue
uint32_t capacity; // maximum number of items
uint32_t toq; // first item to take
uint32_t eoq; // free space at end of queue
void *items; // a pointer to where the items are actually stored
} pc_queue; // producer-consumer queue
#endif
static int msg_indexes = 1;
typedef struct {
int index_number;
uint32_t referenceCount; // we might start using this...
unsigned int nheaders;
char *name[16];
char *value[16];
uint32_t contentlength;
char *content;
// for requests
char method[16];
char path[256];
// for responses
int respcode;
} rtsp_message;
#ifdef CONFIG_AIRPLAY_2
int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) {
int response = 0;
void *p = *allocation;
if (p == NULL) {
p = malloc(strlen(s) + 1);
if (p == NULL) {
debug(1, "error allocating memory");
} else {
*allocation = p;
*size = *size + strlen(s) + 1;
uint8_t *b = (uint8_t *)p;
*b = strlen(s);
p = p + 1;
memcpy(p, s, strlen(s));
response = 1;
}
} else {
p = realloc(p, *size + strlen(s) + 1);
if (p == NULL) { // assuming we never allocate a zero byte space
debug(1, "error reallocating memory");
} else {
*allocation = p;
uint8_t *b = (uint8_t *)p + *size;
*b = strlen(s);
p = p + *size + 1;
memcpy(p, s, strlen(s));
*size = *size + strlen(s) + 1;
response = 1;
}
}
return response;
}
static void pkString_make(char *str, size_t str_size, const char *device_id) {
uint8_t public_key[32];
if (str_size < 2 * sizeof(public_key) + 1) {
warn("Insufficient string size");
str[0] = '\0';
return;
}
pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, device_id);
char *ptr = str;
for (size_t i = 0; i < sizeof(public_key); i++)
ptr += sprintf(ptr, "%02x", public_key[i]);
}
#endif
#ifdef CONFIG_AIRPLAY_2
void build_bonjour_strings(rtsp_conn_info *conn) {
#else
void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
#endif
int entry_number = 0;
// make up a firmware version
#ifdef CONFIG_USE_GIT_VERSION_STRING
if (git_version_string[0] != '\0')
snprintf(firmware_version, sizeof(firmware_version), "fv=%s", git_version_string);
else
#endif
snprintf(firmware_version, sizeof(firmware_version), "fv=%s", PACKAGE_VERSION);
#ifdef CONFIG_AIRPLAY_2
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
snprintf(pkString, sizeof(pkString), "pk=");
pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
config.airplay_device_id);
txt_records[entry_number++] = "cn=0,1";
txt_records[entry_number++] = "da=true";
txt_records[entry_number++] = "et=0,4";
txt_records[entry_number++] = ap1_featuresString;
txt_records[entry_number++] = firmware_version;
txt_records[entry_number++] = "md=2";
txt_records[entry_number++] = "am=Shairport Sync";
txt_records[entry_number++] = "sf=0x4";
txt_records[entry_number++] = "tp=UDP";
txt_records[entry_number++] = "vn=65537";
txt_records[entry_number++] = "vs=366.0";
txt_records[entry_number++] = pkString;
txt_records[entry_number++] = NULL;
#else
// here, just replicate what happens in mdns.h when using those #defines
txt_records[entry_number++] = "sf=0x4";
txt_records[entry_number++] = firmware_version;
txt_records[entry_number++] = "am=ShairportSync";
txt_records[entry_number++] = "vs=105.1";
txt_records[entry_number++] = "tp=TCP,UDP";
txt_records[entry_number++] = "vn=65537";
#ifdef CONFIG_METADATA
if (config.get_coverart == 0)
txt_records[entry_number++] = "md=0,2";
else
txt_records[entry_number++] = "md=0,1,2";
#endif
txt_records[entry_number++] = "ss=16";
txt_records[entry_number++] = "sr=44100";
txt_records[entry_number++] = "da=true";
txt_records[entry_number++] = "sv=false";
txt_records[entry_number++] = "et=0,1";
txt_records[entry_number++] = "ek=1";
txt_records[entry_number++] = "cn=0,1";
txt_records[entry_number++] = "ch=2";
txt_records[entry_number++] = "txtvers=1";
if (config.password == 0)
txt_records[entry_number++] = "pw=false";
else
txt_records[entry_number++] = "pw=true";
txt_records[entry_number++] = NULL;
#endif
#ifdef CONFIG_AIRPLAY_2
// make up a secondary set of text records
entry_number = 0;
secondary_txt_records[entry_number++] = "srcvers=366.0";
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
secondary_txt_records[entry_number++] = deviceIdString;
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
secondary_txt_records[entry_number++] = featuresString;
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
config.airplay_statusflags);
secondary_txt_records[entry_number++] = statusflagsString;
secondary_txt_records[entry_number++] = "protovers=1.1";
secondary_txt_records[entry_number++] = "acl=0";
secondary_txt_records[entry_number++] = "rsf=0x0";
secondary_txt_records[entry_number++] = firmware_version;
secondary_txt_records[entry_number++] = "model=Shairport Sync";
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
secondary_txt_records[entry_number++] = piString;
if ((conn != NULL) && (conn->airplay_gid != 0)) {
snprintf(gidString, sizeof(gidString), "gid=%s", conn->airplay_gid);
} else {
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_pi);
}
secondary_txt_records[entry_number++] = gidString;
if ((conn != NULL) && (conn->groupContainsGroupLeader != 0))
secondary_txt_records[entry_number++] = "gcgl=1";
else
secondary_txt_records[entry_number++] = "gcgl=0";
if ((conn != NULL) && (conn->airplay_gid != 0)) // if it's in a group
secondary_txt_records[entry_number++] = "isGroupLeader=0";
secondary_txt_records[entry_number++] = pkString;
secondary_txt_records[entry_number++] = NULL;
#endif
}
#ifdef CONFIG_METADATA
typedef struct {
uint32_t type;
uint32_t code;
char *data;
uint32_t length;
rtsp_message *carrier;
} metadata_package;
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
const char *name) {
if (name)
debug(2, "Creating metadata queue \"%s\".", name);
else
debug(1, "Creating an unnamed metadata queue.");
pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
the_queue->item_size = item_size;
the_queue->items = items;
the_queue->count = 0;
the_queue->capacity = number_of_items;
the_queue->toq = 0;
the_queue->eoq = 0;
if (name == NULL)
the_queue->name = NULL;
else
the_queue->name = strdup(name);
}
void pc_queue_delete(pc_queue *the_queue) {
if (the_queue->name)
debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
else
debug(1, "Deleting an unnamed metadata queue.");
if (the_queue->name != NULL)
free(the_queue->name);
// debug(2, "destroying pc_queue_item_removed_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
// debug(2, "destroying pc_queue_item_added_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
// debug(2, "destroying pc_queue_lock");
pthread_mutex_destroy(&the_queue->pc_queue_lock);
// debug(2, "destroying signals and locks done");
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block);
int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) {
return send_metadata('ssnc', code, data, length, NULL, block);
}
void pc_queue_cleanup_handler(void *arg) {
// debug(1, "pc_queue_cleanup_handler called.");
pc_queue *the_queue = (pc_queue *)arg;
int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
if (rc)
debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item.");
}
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
int response = 0;
int rc;
if (the_queue) {
if (block == 0) {
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 2);
if (rc == EBUSY)
return EBUSY;
} else
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc)
debug(1, "Error locking for pc_queue_add_item");
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
// leave this out if you want this to return if the queue is already full
// irrespective of the block flag.
/*
while (the_queue->count == the_queue->capacity) {
rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
&the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed");
}
*/
if (the_queue->count < the_queue->capacity) {
uint32_t i = the_queue->eoq;
void *p = the_queue->items + the_queue->item_size * i;
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
memcpy(p, the_stuff, the_queue->item_size);
// update the pointer
i++;
if (i == the_queue->capacity)
// fold pointer if necessary
i = 0;
the_queue->eoq = i;
the_queue->count++;
// debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count,
// the_queue->capacity);
if (the_queue->count == the_queue->capacity)
debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name,
the_queue->count);
rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
if (rc)
debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item",
the_queue->name);
} else {
response = EWOULDBLOCK; // a bit arbitrary, this.
debug(3,
"metadata queue \"%s\": is already full with %d items in it. Not adding this item to "
"the queue.",
the_queue->name, the_queue->count);
}
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Adding an item to a NULL queue");
}
return response;
}
int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
int rc;
if (the_queue) {
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc)
debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
while (the_queue->count == 0) {
rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
if (rc)
debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
}
uint32_t i = the_queue->toq;
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
void *p = the_queue->items + the_queue->item_size * i;
memcpy(the_stuff, p, the_queue->item_size);
// update the pointer
i++;
if (i == the_queue->capacity)
// fold pointer if necessary
i = 0;
the_queue->toq = i;
the_queue->count--;
debug(3, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count,
the_queue->capacity);
rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
if (rc)
debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Removing an item from a NULL queue");
}
return 0;
}
#endif
void lock_player() { debug_mutex_lock(&playing_conn_lock, 1000000, 3); }
void unlock_player() { debug_mutex_unlock(&playing_conn_lock, 3); }
int have_play_lock(rtsp_conn_info *conn) {
int response = 0;
lock_player();
if (playing_conn == conn) // this connection definitely has the play lock
response = 1;
unlock_player();
return response;
}
/*
// return 0 if the playing_conn isn't already locked by someone else and if
// it belongs to the conn passed in.
// remember to release it!
int try_to_get_and_lock_playing_conn(rtsp_conn_info **conn) {
int response = -1;
if (pthread_mutex_trylock(&playing_conn_lock) == 0) {
response = 0;
*conn = playing_conn;
}
return response;
}
void release_hold_on_play_lock(__attribute__((unused)) rtsp_conn_info *conn) {
pthread_mutex_unlock(&playing_conn_lock);
}
*/
// make conn no longer the playing_conn
void release_play_lock(rtsp_conn_info *conn) {
if (conn != NULL)
debug(2, "Connection %d: release play lock.", conn->connection_number);
else
debug(2, "Release play lock.");
lock_player();
if (playing_conn == conn) { // if we have the player
if (conn != NULL)
debug(2, "Connection %d: play lock released.", conn->connection_number);
else
debug(2, "Play lock released.");
playing_conn = NULL; // let it go
}
unlock_player();
}
// make conn the playing_conn, and kill the current session if permitted
int get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) {
if (conn != NULL)
debug(2, "Connection %d: request play lock.", conn->connection_number);
else if (playing_conn != NULL)
debug(2, "Connection %d: request release.", playing_conn->connection_number);
else
debug(2, "Request release of non-existent player.");
// returns -1 if it failed, 0 if it succeeded and 1 if it succeeded but
// interrupted an existing session
int response = 0;
int have_the_player = 0;
int should_wait = 0; // this will be true if you're trying to break in to the current session
int interrupting_current_session = 0;
// try to become the current playing_conn
lock_player(); // don't let it change while we are looking at it...
if (playing_conn == NULL) {
playing_conn = conn;
have_the_player = 1;
} else if (playing_conn == conn) {
have_the_player = 1;
if (conn != NULL)
warn("Duplicate attempt to acquire the player by the same connection, by the look of it!");
} else if (playing_conn->stop) {
debug(1, "Connection %d: Waiting for Connection %d to stop playing.", conn->connection_number,
playing_conn->connection_number);
should_wait = 1;
} else if (allow_session_interruption != 0) {
debug(2, "Asking Connection %d to stop playing.", playing_conn->connection_number);
playing_conn->stop = 1;
interrupting_current_session = 1;
should_wait = 1;
pthread_cancel(playing_conn->thread); // asking the RTSP thread to exit
}
unlock_player();
if (should_wait) {
int time_remaining = 3000000; // must be signed, as it could go negative...
while ((time_remaining > 0) && (have_the_player == 0)) {
lock_player(); // get it
if (playing_conn == NULL) {
playing_conn = conn;
have_the_player = 1;
}
unlock_player();
if (have_the_player == 0) {
usleep(100000);
time_remaining -= 100000;
}
}
if ((have_the_player == 1) && (interrupting_current_session == 1)) {
if (conn != NULL)
debug(2, "Connection %d: Got player lock", conn->connection_number);
else
debug(2, "Player released.");
response = 1;
} else {
debug(2, "Connection %d: failed to get player lock after waiting.", conn->connection_number);
response = -1;
}
}
if ((have_the_player == 1) && (interrupting_current_session == 0)) {
if (conn != NULL)
debug(2, "Connection %d: Got player lock.", conn->connection_number);
else
debug(2, "Player released.");
response = 0;
}
return response;
}
void player_watchdog_thread_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(3, "Connection %d: Watchdog Exit.", conn->connection_number);
}
void *player_watchdog_thread_code(void *arg) {
pthread_cleanup_push(player_watchdog_thread_cleanup_handler, arg);
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
do {
usleep(2000000); // check every two seconds
// debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number);
#ifdef CONFIG_AIRPLAY_2
if ((config.dont_check_timeout == 0) && (config.timeout != 0) && (conn->airplay_type == ap_1)) {
#else
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
#endif
debug_mutex_lock(&conn->watchdog_mutex, 1000, 0);
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
uint64_t time_since_last_bark =
(get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
conn->watchdog_barks++;
if (conn->watchdog_barks == 1) {
// debuglev = 3; // tell us everything.
debug(1,
"Connection %d: As Yeats almost said, \"Too long a silence / can make a stone "
"of the heart\".",
conn->connection_number);
conn->stop = 1;
pthread_cancel(conn->thread);
} else if (conn->watchdog_barks == 3) {
if ((config.cmd_unfixable) && (config.unfixable_error_reported == 0)) {
config.unfixable_error_reported = 1;
command_execute(config.cmd_unfixable, "unable_to_cancel_play_session", 1);
} else {
die("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.",
conn->connection_number);
}
}
}
}
}
} while (1);
pthread_cleanup_pop(0); // should never happen
pthread_exit(NULL);
}
// keep track of the threads we have spawned so we can join() them
static int nconns = 0;
static void track_thread(rtsp_conn_info *conn) {
debug_mutex_lock(&conns_lock, 1000000, 3);
// look for an empty slot first
int i = 0;
int found = 0;
while ((i < nconns) && (found == 0)) {
if (conns[i] == NULL)
found = 1;
else
i++;
}
if (found != 0) {
conns[i] = conn;
} else {
// make space for a new element
conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1));
if (conns) {
conns[nconns] = conn;
nconns++;
} else {
die("could not reallocate memory for conns");
}
}
debug_mutex_unlock(&conns_lock, 3);
}
// note: connection numbers start at 1, so an except_this_one value of zero means "all threads"
void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_one) {
// if the stream category is unspecified_stream_category
// all categories are elegible for cancellation
// otherwise just the category itself
debug_mutex_lock(&conns_lock, 1000000, 3);
int i;
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running != 0) &&
(conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
pthread_cancel(conns[i]->thread);
debug(2, "Connection %d: cancelled.", conns[i]->connection_number);
}
}
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running != 0) &&
(conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
pthread_join(conns[i]->thread, NULL);
debug(2, "Connection %d: joined.", conns[i]->connection_number);
free(conns[i]);
conns[i] = NULL;
}
}
debug_mutex_unlock(&conns_lock, 3);
}
int old_connection_count = -1;
void cleanup_threads(void) {
void *retval;
int i;
int connection_count = 0;
// debug(2, "culling threads.");
debug_mutex_lock(&conns_lock, 1000000, 3);
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running == 0)) {
debug(3, "found RTSP connection thread %d in a non-running state.",
conns[i]->connection_number);
pthread_join(conns[i]->thread, &retval);
debug(2, "Connection %d: deleted in cleanup.", conns[i]->connection_number);
free(conns[i]);
conns[i] = NULL;
}
if (conns[i] != NULL)
connection_count++;
}
debug_mutex_unlock(&conns_lock, 3);
if (old_connection_count != connection_count) {
if (connection_count == 0)
debug(2, "No active connections.");
else if (connection_count == 1)
debug(2, "One active connection.");
else
debug(2, "%d active connections.", connection_count);
old_connection_count = connection_count;
}
}
// park a null at the line ending, and return the next line pointer
// accept \r, \n, or \r\n
static char *nextline(char *in, int inbuf) {
char *out = NULL;
while (inbuf) {
if (*in == '\r') {
*in++ = 0;
out = in;
inbuf--;
}
if ((*in == '\n') && (inbuf)) {
*in++ = 0;
out = in;
}
if (out)
break;
in++;
inbuf--;
}
return out;
}
void msg_retain(rtsp_message *msg) {
int rc = pthread_mutex_lock(&reference_counter_lock);
if (rc)
debug(1, "Error %d locking reference counter lock");
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
debug(3, "msg_free increment reference counter message %d to %d.", msg->index_number,
msg->referenceCount);
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
debug(1, "Error %d unlocking reference counter lock");
} else {
debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t)msg);
}
}
rtsp_message *msg_init(void) {
rtsp_message *msg = malloc(sizeof(rtsp_message));
if (msg) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
debug(3, "msg_init message %d", msg->index_number);
} else {
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
// debug(1,"msg_init -- create item %d.", msg->index_number);
return msg;
}
int msg_add_header(rtsp_message *msg, char *name, char *value) {
if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
warn("too many headers?!");
return 1;
}
msg->name[msg->nheaders] = strdup(name);
msg->value[msg->nheaders] = strdup(value);
msg->nheaders++;
return 0;
}
char *msg_get_header(rtsp_message *msg, char *name) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++)
if (!strcasecmp(msg->name[i], name))
return msg->value[i];
return NULL;
}
void _debug_print_msg_headers(const char *filename, const int linenumber, int level,
rtsp_message *msg) {
unsigned int i;
if (msg->respcode != 0)
_debug(filename, linenumber, level, " Response Code: %d.", msg->respcode);
for (i = 0; i < msg->nheaders; i++) {
_debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i],
msg->value[i]);
}
}
/*
static void debug_print_msg_content(int level, rtsp_message *msg) {
if (msg->contentlength) {
char *obf = malloc(msg->contentlength * 2 + 1);
if (obf) {
char *obfp = obf;
int obfc;
for (obfc = 0; obfc < msg->contentlength; obfc++) {
snprintf(obfp, 3, "%02X", msg->content[obfc]);
obfp += 2;
};
*obfp = 0;
debug(level, "Content (hex): \"%s\"", obf);
free(obf);
} else {
debug(level, "Can't allocate space for debug buffer");
}
} else {
debug(level, "No content");
}
}
*/
void msg_free(rtsp_message **msgh) {
debug_mutex_lock(&reference_counter_lock, 1000, 0);
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
if (msg->referenceCount)
debug(3, "msg_free decrement reference counter message %d to %d", msg->index_number,
msg->referenceCount);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
free(msg->name[i]);
free(msg->value[i]);
}
if (msg->content)
free(msg->content);
// debug(1,"msg_free item %d -- free.",msg->index_number);
uintptr_t index = (msg->index_number) & 0xFFFF;
if (index == 0)
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
debug(3, "msg_free freed message %d", msg->index_number);
free(msg);
} else {
// debug(1,"msg_free item %d -- decrement reference to
// %d.",msg->index_number,msg->referenceCount);
}
} else if (*msgh != NULL) {
debug(1,
"msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
"%d.",
(uintptr_t)*msgh);
}
debug_mutex_unlock(&reference_counter_lock, 0);
}
int msg_handle_line(rtsp_message **pmsg, char *line) {
rtsp_message *msg = *pmsg;
if (!msg) {
msg = msg_init();
*pmsg = msg;
char *sp, *p;
sp = NULL; // this is to quieten a compiler warning
debug(3, "RTSP Message Received: \"%s\".", line);
p = strtok_r(line, " ", &sp);
if (!p)
goto fail;
strncpy(msg->method, p, sizeof(msg->method) - 1);
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
strncpy(msg->path, p, sizeof(msg->path) - 1);
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
if (strcmp(p, "RTSP/1.0"))
goto fail;
return -1;
}
if (strlen(line)) {
char *p;
p = strstr(line, ": ");
if (!p) {
warn("bad header: >>%s<<", line);
goto fail;
}
*p = 0;
p += 2;
msg_add_header(msg, line, p);
debug(3, " %s: %s.", line, p);
return -1;
} else {
char *cl = msg_get_header(msg, "Content-Length");
if (cl)
return atoi(cl);
else
return 0;
}
fail:
debug(3, "msg_handle_line fail");
msg_free(pmsg);
*pmsg = NULL;
return 0;
}
#ifdef CONFIG_AIRPLAY_2
void add_flush_request(int flushNow, uint32_t flushFromSeq, uint32_t flushFromTS,
uint32_t flushUntilSeq, uint32_t flushUntilTS, rtsp_conn_info *conn) {
// immediate flush requests are added sequentially. Don't know how more than one could arise, TBH
flush_request_t **t = &conn->flush_requests;
int done = 0;
do {
flush_request_t *u = *t;
if ((u == NULL) || ((u->flushNow == 0) && (flushNow != 0)) ||
(flushFromSeq < u->flushFromSeq) ||
((flushFromSeq == u->flushFromSeq) && (flushFromTS < u->flushFromTS))) {
flush_request_t *n = (flush_request_t *)calloc(sizeof(flush_request_t), 1);
n->flushNow = flushNow;
n->flushFromSeq = flushFromSeq;
n->flushFromTS = flushFromTS;
n->flushUntilSeq = flushUntilSeq;
n->flushUntilTS = flushUntilTS;
n->next = u;
*t = n;
done = 1;
} else {
t = &u->next;
}
} while (done == 0);
}
void display_all_flush_requests(rtsp_conn_info *conn) {
if (conn->flush_requests == NULL) {
debug(1, "No flush requests.");
} else {
flush_request_t *t = conn->flush_requests;
do {
if (t->flushNow) {
debug(1, "immediate flush to untilSeq: %u, untilTS: %u.", t->flushUntilSeq,
t->flushUntilTS);
} else {
debug(1, "fromSeq: %u, fromTS: %u, to untilSeq: %u, untilTS: %u.", t->flushFromSeq,
t->flushFromTS, t->flushUntilSeq, t->flushUntilTS);
}
t = t->next;
} while (t != NULL);
}
}
int rtsp_message_contains_plist(rtsp_message *message) {
int reply = 0; // assume there is no plist in the message
if ((message->contentlength >= strlen("bplist00")) &&
(strstr(message->content, "bplist00") == message->content))
reply = 1;
return reply;
}
plist_t plist_from_rtsp_content(rtsp_message *message) {
plist_t the_plist = NULL;
if (rtsp_message_contains_plist(message)) {
plist_from_memory(message->content, message->contentlength, &the_plist);
}
return the_plist;
}
char *plist_content(plist_t the_plist) {
// caller must free the returned character buffer
// convert it to xml format
uint32_t size;
char *plist_out = NULL;
plist_to_xml(the_plist, &plist_out, &size);
// put it into a NUL-terminated string
char *reply = malloc(size + 1);
if (reply) {
memcpy(reply, plist_out, size);
reply[size] = '\0';
}
if (the_plist)
plist_free(the_plist);
if (plist_out)
free(plist_out);
return reply;
}
// caller must free the returned character buffer
char *rtsp_plist_content(rtsp_message *message) {
char *reply = NULL;
// first, check if it has binary plist content
if (rtsp_message_contains_plist(message)) {
// get the plist from the content
plist_t the_plist = plist_from_rtsp_content(message);
// convert it to xml format
uint32_t size;
char *plist_out = NULL;
plist_to_xml(the_plist, &plist_out, &size);
// put it into a NUL-terminated string
reply = malloc(size + 1);
if (reply) {
memcpy(reply, plist_out, size);
reply[size] = '\0';
}
if (the_plist)
plist_free(the_plist);
if (plist_out)
free(plist_out);
}
return reply;
}
#endif
void _debug_log_rtsp_message(const char *filename, const int linenumber, int level, char *prompt,
rtsp_message *message) {
if (level > debuglev)
return;
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
_debug(filename, linenumber, level, prompt);
_debug_print_msg_headers(filename, linenumber, level, message);
#ifdef CONFIG_AIRPLAY_2
char *plist_content = rtsp_plist_content(message);
if (plist_content) {
_debug(filename, linenumber, level, " Content Plist (as XML):\n--\n%s--", plist_content);
free(plist_content);
} else
#endif
{
_debug(filename, linenumber, level, " No Content Plist. Content length: %d.",
message->contentlength);
}
}
#define debug_log_rtsp_message(level, prompt, message) \
_debug_log_rtsp_message(__FILE__, __LINE__, level, prompt, message)
#define debug_print_msg_headers(level, message) \
_debug_print_msg_headers(__FILE__, __LINE__, level, message)
#ifdef CONFIG_AIRPLAY_2
static void buf_add(sized_buffer *buf, uint8_t *in, size_t in_len) {
if (buf->length + in_len > buf->size) {
buf->size = buf->length + in_len + 2048; // Extra legroom to avoid future memcpy's
uint8_t *new = malloc(buf->size);
memcpy(new, buf->data, buf->length);
free(buf->data);
buf->data = new;
}
memcpy(buf->data + buf->length, in, in_len);
buf->length += in_len;
}
static void buf_drain(sized_buffer *buf, ssize_t len) {
if (len < 0 || (size_t)len >= buf->length) {
free(buf->data);
memset(buf, 0, sizeof(sized_buffer));
return;
}
memmove(buf->data, buf->data + len, buf->length - len);
buf->length -= len;
}
static size_t buf_remove(sized_buffer *buf, uint8_t *out, size_t out_len) {
size_t bytes = (buf->length > out_len) ? out_len : buf->length;
memcpy(out, buf->data, bytes);
buf_drain(buf, bytes);
return bytes;
}
static ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count) {
uint8_t in[4096];
uint8_t *plain;
size_t plain_len;
// If there is leftover decoded content from the last pass just return that
if (ctx->plaintext_read_buffer.length > 0) {
return buf_remove(&ctx->plaintext_read_buffer, buf, count);
}
do {
ssize_t got = read(fd, in, sizeof(in));
if (got <= 0)
return got;
buf_add(&ctx->encrypted_read_buffer, in, got);
ssize_t consumed = pair_decrypt(&plain, &plain_len, ctx->encrypted_read_buffer.data,
ctx->encrypted_read_buffer.length, ctx->cipher_ctx);
if (consumed < 0)
return -1;
buf_drain(&ctx->encrypted_read_buffer, consumed);
} while (plain_len == 0);
// Fast path, avoids some memcpy + allocs in case of the normal, small message
/* if (ctx->plaintext_read_buffer.len == 0 && plain_len < count) {
memcpy(buf, plain, plain_len);
free(plain);
return plain_len;
}
*/
buf_add(&ctx->plaintext_read_buffer, plain, plain_len);
free(plain);
return buf_remove(&ctx->plaintext_read_buffer, buf, count);
}
static ssize_t write_encrypted(int fd, pair_cipher_bundle *ctx, const void *buf, size_t count) {
uint8_t *encrypted;
size_t encrypted_len;
ssize_t ret = pair_encrypt(&encrypted, &encrypted_len, buf, count, ctx->cipher_ctx);
if (ret < 0) {
debug(1, pair_cipher_errmsg(ctx->cipher_ctx));
return -1;
}
size_t remain = encrypted_len;
while (remain > 0) {
ssize_t wrote = write(fd, encrypted + (encrypted_len - remain), remain);
if (wrote <= 0) {
free(encrypted);
return wrote;
}
remain -= wrote;
}
free(encrypted);
return count;
}
/*
static ssize_t write_encrypted(rtsp_conn_info *conn, const void *buf, size_t count) {
uint8_t *encrypted;
size_t encrypted_len;
ssize_t ret =
pair_encrypt(&encrypted, &encrypted_len, buf, count, conn->ap2_pairing_context.cipher_ctx);
if (ret < 0) {
debug(1, pair_cipher_errmsg(conn->ap2_pairing_context.cipher_ctx));
return -1;
}
size_t remain = encrypted_len;
while (remain > 0) {
ssize_t wrote = write(conn->fd, encrypted + (encrypted_len - remain), remain);
if (wrote <= 0) {
free(encrypted);
return wrote;
}
remain -= wrote;
}
free(encrypted);
return count;
}
*/
#endif
ssize_t timed_read_from_rtsp_connection(rtsp_conn_info *conn, uint64_t wait_time, void *buf,
size_t count) {
// note: a wait time of zero means wait forever
ssize_t result = 0; // closed
if (conn->fd > 0) {
int64_t remaining_time = 0;
uint64_t time_to_wait_to = get_absolute_time_in_ns();
;
time_to_wait_to = time_to_wait_to + wait_time;
int flags = 1;
if (setsockopt(conn->fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags))) {
debug(1, "can't enable keepalive checking on the RTSP socket");
}
// remaining_time will be zero if wait_time is zero
if (wait_time != 0) {
remaining_time = time_to_wait_to - get_absolute_time_in_ns();
}
do {
struct timeval tv;
tv.tv_sec = remaining_time / 1000000000; // seconds
tv.tv_usec = (remaining_time % 1000000000) / 1000; // microseconds
if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) != 0) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "could not set time limit on timed_read_from_rtsp_connection -- error %d \"%s\".",
errno, errorstring);
}
#ifdef CONFIG_AIRPLAY_2
if (conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) {
conn->ap2_pairing_context.control_cipher_bundle.is_encrypted = 1;
result =
read_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, buf, count);
} else {
result = read(conn->fd, buf, count);
}
#else
result = read(conn->fd, buf, count);
#endif
if (wait_time != 0)
remaining_time = time_to_wait_to - get_absolute_time_in_ns();
if (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) && (remaining_time > 0))
debug(1, "remaining time on a timed read is %" PRId64 " ns.", remaining_time);
} while (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) &&
(remaining_time > 0));
} else {
debug(1, "Connection %d: attempt to read from a closed RTSP connection.",
conn->connection_number);
}
return result;
}
#ifdef CONFIG_AIRPLAY_2
void set_client_as_ptp_clock(rtsp_conn_info *conn) {
char timing_list_message[4096] = "";
strncat(timing_list_message, "T ", sizeof(timing_list_message) - 1 - strlen(timing_list_message));
strncat(timing_list_message, (const char *)&conn->client_ip_string,
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
ptp_send_control_message_string(timing_list_message);
}
void clear_ptp_clock() { ptp_send_control_message_string("T"); }
#endif
ssize_t read_from_rtsp_connection(rtsp_conn_info *conn, void *buf, size_t count) {
// first try to read with a timeout, to see if there is any traffic...
// ssize_t response = timed_read_from_rtsp_connection(conn, 20000000000L, buf, count);
// actually don't use a timeout -- OwnTone doesn't supply regular traffic.
ssize_t response = timed_read_from_rtsp_connection(conn, 0, buf, count);
if ((response == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
if (conn->rtsp_link_is_idle == 0) {
debug(1, "Connection %d: RTSP connection is idle.", conn->connection_number);
conn->rtsp_link_is_idle = 1;
conn->udp_clock_sender_is_initialised = 0;
conn->udp_clock_is_initialised = 0;
}
response = timed_read_from_rtsp_connection(conn, 0, buf, count);
}
if (conn->rtsp_link_is_idle == 1) {
conn->rtsp_link_is_idle = 0;
debug(1, "Connection %d: RTSP connection traffic has resumed.", conn->connection_number);
#ifdef CONFIG_AIRPLAY_2
if (conn->airplay_stream_type == realtime_stream) {
conn->last_anchor_info_is_valid = 0;
conn->anchor_remote_info_is_valid = 0;
conn->first_packet_timestamp = 0;
conn->input_frame_rate_starting_point_is_valid = 0;
ab_resync(conn);
}
#else
conn->anchor_remote_info_is_valid = 0;
conn->local_to_remote_time_difference_measurement_time = 0;
conn->local_to_remote_time_difference = 0;
conn->first_packet_timestamp = 0;
conn->input_frame_rate_starting_point_is_valid = 0;
ab_resync(conn);
#endif
}
return response;
}
enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
*the_packet = NULL; // need this for error handling
enum rtsp_read_request_response reply = rtsp_read_request_response_ok;
ssize_t buflen = 4096;
#ifdef CONFIG_METADATA
if ((config.metadata_enabled != 0) && (config.get_coverart != 0))
buflen = 1024 * 256; // big enough for typical picture data, which will be base64 encoded
#endif
int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay
char *buf = malloc(buflen + 1); // add a NUL at the end
if (!buf) {
warn("Connection %d: rtsp_read_request: can't get a buffer.", conn->connection_number);
return (rtsp_read_request_response_error);
}
pthread_cleanup_push(malloc_cleanup, buf);
ssize_t nread;
ssize_t inbuf = 0;
int msg_size = -1;
while (msg_size < 0) {
if (conn->stop != 0) {
debug(3, "Connection %d: Shutdown requested by client.", conn->connection_number);
reply = rtsp_read_request_response_immediate_shutdown_requested;
goto shutdown;
}
nread = read_from_rtsp_connection(conn, buf + inbuf, buflen - inbuf);
if (nread == 0) {
// a blocking read that returns zero means eof -- implies connection closed by client
debug(1, "Connection %d: Connection closed by client.", conn->connection_number);
reply = rtsp_read_request_response_channel_closed;
// Note: the socket will be closed when the thread exits
goto shutdown;
}
// An ETIMEDOUT error usually means keepalive has failed.
if (nread < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!",
conn->connection_number);
continue;
}
if (errno != ECONNRESET) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
if (errno != 0)
debug(2, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".",
conn->connection_number, errno, (char *)errorstring);
}
reply = rtsp_read_request_response_read_error;
goto shutdown;
}
/* // this outputs the message received
{
void *pt = malloc(nread+1);
memset(pt, 0, nread+1);
memcpy(pt, buf + inbuf, nread);
debug(1, "Incoming string on port: \"%s\"",pt);
free(pt);
}
*/
inbuf += nread;
char *next;
while (msg_size < 0 && (next = nextline(buf, inbuf))) {
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.",
conn->connection_number);
reply = rtsp_read_request_response_bad_packet;
goto shutdown;
}
inbuf -= next - buf;
if (inbuf)
memmove(buf, next, inbuf);
}
}
if (msg_size > buflen) {
buf = realloc(buf, msg_size + 1);
if (!buf) {
warn("Connection %d: too much content.", conn->connection_number);
reply = rtsp_read_request_response_error;
goto shutdown;
}
buflen = msg_size;
}
uint64_t threshold_time =
get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now
int warning_message_sent = 0;
// const size_t max_read_chunk = 1024 * 1024 / 16;
while (inbuf < msg_size) {
// we are going to read the stream in chunks and time how long it takes to
// do so.
// If it's taking too long, (and we find out about it), we will send an
// error message as
// metadata
if (warning_message_sent == 0) {
uint64_t time_now = get_absolute_time_in_ns();
if (time_now > threshold_time) { // it's taking too long
debug(1, "Error receiving metadata from source -- transmission seems "
"to be stalled.");
#ifdef CONFIG_METADATA
send_ssnc_metadata('stal', NULL, 0, 1);
#endif
warning_message_sent = 1;
}
}
if (conn->stop != 0) {
debug(1, "RTSP shutdown requested.");
reply = rtsp_read_request_response_immediate_shutdown_requested;
goto shutdown;
}
size_t read_chunk = msg_size - inbuf;
// if (read_chunk > max_read_chunk)
// read_chunk = max_read_chunk;
// usleep(80000); // wait about 80 milliseconds between reads of up to max_read_chunk
nread = read_from_rtsp_connection(conn, buf + inbuf, read_chunk);
if (!nread) {
reply = rtsp_read_request_response_error;
goto shutdown;
}
if (nread < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
debug(1, "Getting Error 11 -- EAGAIN from a blocking read!");
continue;
}
if (errno != ECONNRESET) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".",
conn->connection_number, errno, (char *)errorstring);
}
reply = rtsp_read_request_response_read_error;
goto shutdown;
}
inbuf += nread;
}
rtsp_message *msg = *the_packet;
msg->contentlength = inbuf;
msg->content = buf;
char *jp = inbuf + buf;
*jp = '\0';
*the_packet = msg;
shutdown:
if (reply != rtsp_read_request_response_ok) {
msg_free(the_packet);
release_buffer = 1; // allow the buffer to be released
}
pthread_cleanup_pop(release_buffer);
return reply;
}
int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) {
char pkt[4096];
int pktfree = sizeof(pkt);
char *p = pkt;
int n;
unsigned int i;
struct response_t {
int code;
char *string;
};
struct response_t responses[] = {{200, "OK"},
{400, "Bad Request"},
{403, "Unauthorized"},
{404, "Not Found"},
{451, "Unavailable"},
{456, "Header Field Not Valid for Resource"},
{470, "Connection Authorization Required"},
{500, "Internal Server Error"},
{501, "Not Implemented"}};
// 451 is really "Unavailable For Legal Reasons"!
int found = 0;
char *respcode_text = "Unauthorized";
for (i = 0; i < sizeof(responses) / sizeof(struct response_t); i++) {
if (resp->respcode == responses[i].code) {
found = 1;
respcode_text = responses[i].string;
}
}
if (found == 0)
debug(1, "can't find text for response code %d. Using \"%s\" instead.", resp->respcode,
respcode_text);
n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, respcode_text);
pktfree -= n;
p += n;
for (i = 0; i < resp->nheaders; i++) {
// debug(3, " %s: %s.", resp->name[i], resp->value[i]);
n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]);
pktfree -= n;
p += n;
if (pktfree <= 1024) {
debug(1, "Attempted to write overlong RTSP packet 1");
return -1;
}
}
// Here, if there's content, write the Content-Length header ...
if (resp->contentlength) {
debug(2, "Responding with content of length %d", resp->contentlength);
n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength);
pktfree -= n;
p += n;
if (pktfree <= 1024) {
debug(1, "Attempted to write overlong RTSP packet 2");
return -2;
}
}
n = snprintf(p, pktfree, "\r\n");
pktfree -= n;
p += n;
if (resp->contentlength) {
memcpy(p, resp->content, resp->contentlength);
pktfree -= resp->contentlength;
p += resp->contentlength;
}
if (pktfree <= 1024) {
debug(1, "Attempted to write overlong RTSP packet 3");
return -3;
}
// here, if the link is encrypted, better do it
#ifdef CONFIG_AIRPLAY_2
ssize_t reply;
if (conn->ap2_pairing_context.control_cipher_bundle.is_encrypted) {
reply =
write_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, pkt, p - pkt);
} else {
reply = write(conn->fd, pkt, p - pkt);
}
#else
ssize_t reply = write(conn->fd, pkt, p - pkt);
#endif
if (reply == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "msg_write_response error %d: \"%s\".", errno, (char *)errorstring);
return -4;
}
if (reply != p - pkt) {
debug(1, "msg_write_response error -- requested bytes: %d not fully written: %d.", p - pkt,
reply);
return -5;
}
return 0;
}
char *get_category_string(airplay_stream_c cat) {
char *category;
switch (cat) {
case unspecified_stream_category:
category = "unspecified stream";
break;
case ptp_stream:
category = "PTP stream";
break;
case ntp_stream:
category = "NTP stream";
break;
case remote_control_stream:
category = "Remote Control stream";
break;
default:
category = "Unexpected stream code";
break;
}
return category;
}
void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req,
rtsp_message *resp) {
debug(2, "Connection %d: RECORD on %s", conn->connection_number,
get_category_string(conn->airplay_stream_category));
// debug_log_rtsp_message(1, "RECORD incoming message", req);
resp->respcode = 200;
}
void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(2, "Connection %d: RECORD", conn->connection_number);
if (have_play_lock(conn)) {
if (conn->player_thread)
warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number);
else {
debug(1, "Connection %d: Classic AirPlay connection from %s:%u to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
activity_monitor_signify_activity(1);
player_prepare_to_play(conn);
player_play(conn); // the thread better be 0
}
resp->respcode = 200;
// I think this is for telling the client what the absolute minimum latency
// actually is,
// and when the client specifies a latency, it should be added to this figure.
// Thus, [the old version of] AirPlay's latency figure of 77175, when added to 11025 gives you
// exactly 88200
// and iTunes' latency figure of 88553, when added to 11025 gives you 99578,
// pretty close to the 99400 we guessed.
msg_add_header(resp, "Audio-Latency", "11025");
char *p;
uint32_t rtptime = 0;
char *hdr = msg_get_header(req, "RTP-Info");
if (hdr) {
// debug(1,"FLUSH message received: \"%s\".",hdr);
// get the rtp timestamp
p = strstr(hdr, "rtptime=");
if (p) {
p = strchr(p, '=');
if (p) {
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
// rtptime--;
// debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime);
player_flush(rtptime, conn);
}
}
}
} else {
warn("Connection %d RECORD received without having the player (no ANNOUNCE?)",
conn->connection_number);
resp->respcode = 451;
}
}
#ifdef CONFIG_AIRPLAY_2
void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(2, "GET /info:", req);
if (rtsp_message_contains_plist(req)) { // it's stage one
// get version of AirPlay -- it might be too old. Not using it yet.
char *hdr = msg_get_header(req, "User-Agent");
if (hdr) {
if (strstr(hdr, "AirPlay/") == hdr) {
hdr = hdr + strlen("AirPlay/");
// double airplay_version = 0.0;
// airplay_version = atof(hdr);
debug(2, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number,
hdr);
}
}
plist_t info_plist = NULL;
plist_from_memory(req->content, req->contentlength, &info_plist);
plist_t qualifier = plist_dict_get_item(info_plist, "qualifier");
if (qualifier == NULL) {
debug(1, "GET /info Stage 1: plist->qualifier was NULL");
goto user_fail;
}
if (plist_array_get_size(qualifier) < 1) {
debug(1, "GET /info Stage 1: plist->qualifier array length < 1");
goto user_fail;
}
plist_t qualifier_array_value = plist_array_get_item(qualifier, 0);
char *qualifier_array_val_cstr;
plist_get_string_val(qualifier_array_value, &qualifier_array_val_cstr);
if (qualifier_array_val_cstr == NULL) {
debug(1, "GET /info Stage 1: first item in qualifier array not a string");
goto user_fail;
}
debug(2, "GET /info Stage 1: qualifier: %s", qualifier_array_val_cstr);
plist_free(info_plist);
free(qualifier_array_val_cstr);
// uint8_t bt_addr[6] = {0xB8, 0x27, 0xEB, 0xB7, 0xD4, 0x0E};
plist_t response_plist = NULL;
plist_from_xml((const char *)plists_get_info_response_xml, plists_get_info_response_xml_len,
&response_plist);
if (response_plist == NULL) {
debug(1, "GET /info Stage 1: response plist not created from XML!");
} else {
void *qualifier_response_data = NULL;
size_t qualifier_response_data_length = 0;
if (add_pstring_to_malloc("acl=0", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc(deviceIdString, &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc(featuresString, &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("rsf=0x0", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("flags=0x4", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("model=Shairport Sync", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("manufacturer=", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("serialNumber=", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("protovers=1.1", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("srcvers=366.0", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc(piString, &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc(gidString, &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
if (add_pstring_to_malloc("gcgl=0", &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
snprintf(pkString, sizeof(pkString), "pk=");
pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
config.airplay_device_id);
if (add_pstring_to_malloc(pkString, &qualifier_response_data,
&qualifier_response_data_length) == 0)
debug(1, "Problem");
// debug(1,"qualifier_response_data_length: %u.", qualifier_response_data_length);
plist_dict_set_item(response_plist, "txtAirPlay",
plist_new_data(qualifier_response_data, qualifier_response_data_length));
plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features));
plist_dict_set_item(response_plist, "statusFlags",
plist_new_uint(config.airplay_statusflags));
plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id));
plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name));
char *vs = get_version_string();
// plist_dict_set_item(response_plist, "model", plist_new_string(vs));
plist_dict_set_item(response_plist, "model", plist_new_string("Shairport Sync"));
free(vs);
// pkString_make(pkString, sizeof(pkString), config.airplay_device_id);
// plist_dict_set_item(response_plist, "pk", plist_new_string(pkString));
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
if (resp->contentlength == 0)
debug(1, "GET /info Stage 1: response bplist not created!");
plist_free(response_plist);
free(qualifier_response_data);
}
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
debug_log_rtsp_message(2, "GET /info Stage 1 Response:", resp);
resp->respcode = 200;
return;
user_fail:
resp->respcode = 400;
return;
} else { // stage two
plist_t response_plist = NULL;
plist_from_xml((const char *)plists_get_info_response_xml, plists_get_info_response_xml_len,
&response_plist);
plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features));
plist_dict_set_item(response_plist, "statusFlags", plist_new_uint(config.airplay_statusflags));
plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id));
plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name));
char *vs = get_version_string();
// plist_dict_set_item(response_plist, "model", plist_new_string(vs));
plist_dict_set_item(response_plist, "model", plist_new_string("Shairport Sync"));
free(vs);
// pkString_make(pkString, sizeof(pkString), config.airplay_device_id);
// plist_dict_set_item(response_plist, "pk", plist_new_string(pkString));
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
debug_log_rtsp_message(2, "GET /info Stage 2 Response", resp);
resp->respcode = 200;
return;
}
}
void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: FLUSHBUFFERED %s : Content-Length %d", conn->connection_number,
req->path, req->contentlength);
debug_log_rtsp_message(2, "FLUSHBUFFERED request", req);
uint64_t flushFromSeq = 0;
uint64_t flushFromTS = 0;
uint64_t flushUntilSeq = 0;
uint64_t flushUntilTS = 0;
int flushFromValid = 0;
plist_t messagePlist = plist_from_rtsp_content(req);
if (messagePlist != NULL) {
plist_t item = plist_dict_get_item(messagePlist, "flushFromSeq");
if (item == NULL) {
debug(2, "Can't find a flushFromSeq");
} else {
flushFromValid = 1;
plist_get_uint_val(item, &flushFromSeq);
debug(2, "flushFromSeq is %" PRId64 ".", flushFromSeq);
}
item = plist_dict_get_item(messagePlist, "flushFromTS");
if (item == NULL) {
if (flushFromValid != 0)
debug(1, "flushFromSeq without flushFromTS!");
else
debug(2, "Can't find a flushFromTS");
} else {
plist_get_uint_val(item, &flushFromTS);
if (flushFromValid == 0)
debug(1, "flushFromTS without flushFromSeq!");
debug(2, "flushFromTS is %" PRId64 ".", flushFromTS);
}
item = plist_dict_get_item(messagePlist, "flushUntilSeq");
if (item == NULL) {
debug(1, "Can't find the flushUntilSeq");
} else {
plist_get_uint_val(item, &flushUntilSeq);
debug(2, "flushUntilSeq is %" PRId64 ".", flushUntilSeq);
}
item = plist_dict_get_item(messagePlist, "flushUntilTS");
if (item == NULL) {
debug(1, "Can't find the flushUntilTS");
} else {
plist_get_uint_val(item, &flushUntilTS);
debug(2, "flushUntilTS is %" PRId64 ".", flushUntilTS);
}
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
// a flush with from... components will not be followed by a setanchor (i.e. a play)
// if it's a flush that will be followed by a setanchor (i.e. a play) then stop play now.
if (flushFromValid == 0)
conn->ap2_play_enabled = 0;
// add the exact request as made to the linked list (not used for anything but diagnostics now)
// int flushNow = 0;
// if (flushFromValid == 0)
// flushNow = 1;
// add_flush_request(flushNow, flushFromSeq, flushFromTS, flushUntilSeq, flushUntilTS, conn);
// now, if it's an immediate flush, replace the existing request, if any
// but it if's a deferred flush and there is an existing deferred request,
// only update the flushUntil stuff -- that seems to preserve
// the intended semantics
// so, always replace these
conn->ap2_flush_until_sequence_number = flushUntilSeq;
conn->ap2_flush_until_rtp_timestamp = flushUntilTS;
if ((conn->ap2_flush_requested != 0) && (conn->ap2_flush_from_valid != 0) &&
(flushFromValid != 0)) {
// if there is a request already, and it's a deferred request, and the current request is also
// deferred... do nothing! -- leave the starting point in place. Yeah, yeah, we know de
// Morgan's Law, but this seems clearer
} else {
conn->ap2_flush_from_sequence_number = flushFromSeq;
conn->ap2_flush_from_rtp_timestamp = flushFromTS;
}
conn->ap2_flush_from_valid = flushFromValid;
conn->ap2_flush_requested = 1;
// reflect the possibly updated flush request
// add_flush_request(flushNow, conn->ap2_flush_from_sequence_number,
// conn->ap2_flush_from_rtp_timestamp, conn->ap2_flush_until_sequence_number,
// conn->ap2_flush_until_rtp_timestamp, conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
if (flushFromValid)
debug(2, "Deferred Flush Requested");
else
debug(2, "Immediate Flush Requested");
plist_free(messagePlist);
// display_all_flush_requests(conn);
}
resp->respcode = 200;
}
void handle_setrate(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: SETRATE %s : Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, "SETRATE request -- unimplemented", req);
resp->respcode = 501; // Not Implemented
}
void handle_unimplemented_ap1(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(1, "request not recognised for AirPlay 1 operation", req);
resp->respcode = 501;
}
void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: SETRATEANCHORI %s :: Content-Length %d", conn->connection_number,
req->path, req->contentlength);
plist_t messagePlist = plist_from_rtsp_content(req);
if (messagePlist != NULL) {
pthread_cleanup_push(plist_cleanup, (void *)messagePlist);
plist_t item = plist_dict_get_item(messagePlist, "networkTimeSecs");
if (item != NULL) {
plist_t item_2 = plist_dict_get_item(messagePlist, "networkTimeTimelineID");
if (item_2 == NULL) {
debug(1, "Can't identify the Clock ID of the player.");
} else {
uint64_t nid;
plist_get_uint_val(item_2, &nid);
debug(2, "networkTimeTimelineID \"%" PRIx64 "\".", nid);
conn->networkTimeTimelineID = nid;
}
uint64_t networkTimeSecs;
plist_get_uint_val(item, &networkTimeSecs);
debug(2, "anchor networkTimeSecs is %" PRIu64 ".", networkTimeSecs);
item = plist_dict_get_item(messagePlist, "networkTimeFrac");
uint64_t networkTimeFrac;
plist_get_uint_val(item, &networkTimeFrac);
debug(2, "anchor networkTimeFrac is 0%" PRIu64 ".", networkTimeFrac);
// it looks like the networkTimeFrac is a fraction where the msb is work 1/2, the
// next 1/4 and so on
// now, convert the network time and fraction into nanoseconds
networkTimeFrac = networkTimeFrac >> 32; // reduce precision to about 1/4 nanosecond
networkTimeFrac = networkTimeFrac * 1000000000;
networkTimeFrac = networkTimeFrac >> 32; // we should now be left with the ns
networkTimeSecs = networkTimeSecs * 1000000000; // turn the whole seconds into ns
uint64_t anchorTimeNanoseconds = networkTimeSecs + networkTimeFrac;
debug(2, "anchorTimeNanoseconds looks like %" PRIu64 ".", anchorTimeNanoseconds);
item = plist_dict_get_item(messagePlist, "rtpTime");
uint64_t rtpTime;
plist_get_uint_val(item, &rtpTime);
// debug(1, "anchor rtpTime is %" PRId64 ".", rtpTime);
uint32_t anchorRTPTime = rtpTime;
int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate);
// debug(1,"anchorRTPTime: %" PRIu32 ", added latency: %" PRId32 ".", anchorRTPTime,
// added_latency);
set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime - added_latency,
anchorTimeNanoseconds);
}
item = plist_dict_get_item(messagePlist, "rate");
if (item != NULL) {
uint64_t rate;
plist_get_uint_val(item, &rate);
debug(3, "anchor rate 0x%016" PRIx64 ".", rate);
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
pthread_cleanup_push(mutex_unlock, &conn->flush_mutex);
conn->ap2_rate = rate;
if ((rate & 1) != 0) {
debug(2, "Connection %d: Start playing, with anchor clock %" PRIx64 ".",
conn->connection_number, conn->networkTimeTimelineID);
activity_monitor_signify_activity(1);
#ifdef CONFIG_METADATA
send_ssnc_metadata('pres', NULL, 0, 1); // resume -- contains cancellation points
#endif
conn->ap2_play_enabled = 1;
} else {
debug(2, "Connection %d: Stop playing.", conn->connection_number);
conn->ap2_play_enabled = 0;
activity_monitor_signify_activity(0);
reset_anchor_info(conn);
#ifdef CONFIG_METADATA
send_ssnc_metadata('paus', NULL, 0, 1); // pause -- contains cancellation points
#endif
if (config.output->stop) {
debug(2, "Connection %d: Stop the output backend.", conn->connection_number);
config.output->stop();
}
}
pthread_cleanup_pop(1); // unlock the conn->flush_mutex
}
pthread_cleanup_pop(1); // plist_free the messagePlist;
} else {
debug(1, "missing plist!");
}
resp->respcode = 200;
}
void handle_get(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(2, "Connection %d: GET %s :: Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, "GET request", req);
if (strcmp(req->path, "/info") == 0) {
handle_get_info(conn, req, resp);
} else {
debug(1, "Unhandled GET, path \"%s\".", req->path);
resp->respcode = 501; // Not Implemented
}
}
#else
void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req,
__attribute((unused)) rtsp_message *resp) {
debug(1, "Connection %d: GET %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
resp->respcode = 500;
}
void handle_post(rtsp_conn_info *conn, rtsp_message *req,
__attribute((unused)) rtsp_message *resp) {
debug(1, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
resp->respcode = 500;
}
#endif
#ifdef CONFIG_AIRPLAY_2
struct pairings {
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
uint8_t public_key[32];
struct pairings *next;
} * pairings;
static struct pairings *pairing_find(const char *device_id) {
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
if (strcmp(device_id, pairing->device_id) == 0)
return pairing;
}
return NULL;
}
static void pairing_add(uint8_t public_key[32], const char *device_id) {
struct pairings *pairing = calloc(1, sizeof(struct pairings));
snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id);
memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
pairing->next = pairings;
pairings = pairing;
}
static void pairing_remove(struct pairings *pairing) {
if (pairing == pairings) {
pairings = pairing->next;
} else {
struct pairings *iter;
for (iter = pairings; iter && (iter->next != pairing); iter = iter->next)
; /* EMPTY */
if (iter)
iter->next = pairing->next;
}
free(pairing);
}
static int pairing_add_cb(uint8_t public_key[32], const char *device_id,
void *cb_arg __attribute__((unused))) {
debug(1, "pair-add cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
if (pairing) {
memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
return 0;
}
pairing_add(public_key, device_id);
return 0;
}
static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id,
void *cb_arg __attribute__((unused))) {
debug(1, "pair-remove cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
if (!pairing) {
debug(1, "pair-remove callback for unknown device");
return -1;
}
pairing_remove(pairing);
return 0;
}
static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg,
void *cb_arg __attribute__((unused))) {
debug(2, "pair-list cb");
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg);
}
}
void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL,
(const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-add returned an error");
resp->respcode = 451;
return;
}
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
debug_log_rtsp_message(2, "pair-add response", resp);
}
void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL,
(const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-list returned an error");
resp->respcode = 451;
return;
}
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
debug_log_rtsp_message(2, "pair-list response", resp);
}
void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL,
(const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-remove returned an error");
resp->respcode = 451;
return;
}
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
debug_log_rtsp_message(2, "pair-remove response", resp);
}
void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int ret;
uint8_t *body = NULL;
size_t body_len = 0;
struct pair_result *result;
debug(2, "Connection %d: pair-verify Content-Length %d", conn->connection_number,
req->contentlength);
if (!conn->ap2_pairing_context.verify_ctx) {
conn->ap2_pairing_context.verify_ctx =
pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, NULL, NULL, config.airplay_device_id);
if (!conn->ap2_pairing_context.verify_ctx) {
debug(1, "Error creating verify context");
resp->respcode = 500; // Internal Server Error
goto out;
}
}
ret = pair_verify(&body, &body_len, conn->ap2_pairing_context.verify_ctx,
(const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, pair_verify_errmsg(conn->ap2_pairing_context.verify_ctx));
resp->respcode = 470; // Connection Authorization Required
goto out;
}
ret = pair_verify_result(&result, conn->ap2_pairing_context.verify_ctx);
if (ret == 0 && result->shared_secret_len > 0) {
conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, result->shared_secret, result->shared_secret_len);
if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) {
debug(1, "Error setting up rtsp control channel ciphering\n");
goto out;
}
}
out:
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
if (body)
msg_add_header(resp, "Content-Type", "application/octet-stream");
debug_log_rtsp_message(2, "pair-verify response", resp);
}
void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int ret;
uint8_t *body = NULL;
size_t body_len = 0;
struct pair_result *result;
debug(2, "Connection %d: handle_pair-setup Content-Length %d", conn->connection_number,
req->contentlength);
if (!conn->ap2_pairing_context.setup_ctx) {
conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.airplay_pin,
NULL, NULL, config.airplay_device_id);
if (!conn->ap2_pairing_context.setup_ctx) {
debug(1, "Error creating setup context");
resp->respcode = 500; // Internal Server Error
goto out;
}
}
ret = pair_setup(&body, &body_len, conn->ap2_pairing_context.setup_ctx,
(const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, pair_setup_errmsg(conn->ap2_pairing_context.setup_ctx));
resp->respcode = 470; // Connection Authorization Required
goto out;
}
ret = pair_setup_result(NULL, &result, conn->ap2_pairing_context.setup_ctx);
if (ret == 0 && result->shared_secret_len > 0) {
// Transient pairing completed (pair-setup step 2), prepare encryption, but
// don't activate yet, the response to this request is still plaintext
conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, result->shared_secret, result->shared_secret_len);
if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) {
debug(1, "Error setting up rtsp control channel ciphering\n");
goto out;
}
}
out:
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
if (body)
msg_add_header(resp, "Content-Type", "application/octet-stream");
debug_log_rtsp_message(2, "pair-setup response", resp);
}
void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
/* Fairplay magic */
static uint8_t server_fp_reply1[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a"
"\x25\x21\xdb\xdf\x31\x2a\xb2\xbf\xb2\x9e\x8d\x23\x2b\x63\x76\xa8\xc8\x18\x70"
"\x1d\x22\xae\x93\xd8\x27\x37\xfe\xaf\x9d\xb4\xfd\xf4\x1c\x2d\xba\x9d\x1f\x49"
"\xca\xaa\xbf\x65\x91\xac\x1f\x7b\xc6\xf7\xe0\x66\x3d\x21\xaf\xe0\x15\x65\x95"
"\x3e\xab\x81\xf4\x18\xce\xed\x09\x5a\xdb\x7c\x3d\x0e\x25\x49\x09\xa7\x98\x31"
"\xd4\x9c\x39\x82\x97\x34\x34\xfa\xcb\x42\xc6\x3a\x1c\xd9\x11\xa6\xfe\x94\x1a"
"\x8a\x6d\x4a\x74\x3b\x46\xc3\xa7\x64\x9e\x44\xc7\x89\x55\xe4\x9d\x81\x55\x00"
"\x95\x49\xc4\xe2\xf7\xa3\xf6\xd5\xba";
static uint8_t server_fp_reply2[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x01\xcf\x32\xa2\x57\x14"
"\xb2\x52\x4f\x8a\xa0\xad\x7a\xf1\x64\xe3\x7b\xcf\x44\x24\xe2\x00\x04\x7e\xfc"
"\x0a\xd6\x7a\xfc\xd9\x5d\xed\x1c\x27\x30\xbb\x59\x1b\x96\x2e\xd6\x3a\x9c\x4d"
"\xed\x88\xba\x8f\xc7\x8d\xe6\x4d\x91\xcc\xfd\x5c\x7b\x56\xda\x88\xe3\x1f\x5c"
"\xce\xaf\xc7\x43\x19\x95\xa0\x16\x65\xa5\x4e\x19\x39\xd2\x5b\x94\xdb\x64\xb9"
"\xe4\x5d\x8d\x06\x3e\x1e\x6a\xf0\x7e\x96\x56\x16\x2b\x0e\xfa\x40\x42\x75\xea"
"\x5a\x44\xd9\x59\x1c\x72\x56\xb9\xfb\xe6\x51\x38\x98\xb8\x02\x27\x72\x19\x88"
"\x57\x16\x50\x94\x2a\xd9\x46\x68\x8a";
static uint8_t server_fp_reply3[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x02\xc1\x69\xa3\x52\xee"
"\xed\x35\xb1\x8c\xdd\x9c\x58\xd6\x4f\x16\xc1\x51\x9a\x89\xeb\x53\x17\xbd\x0d"
"\x43\x36\xcd\x68\xf6\x38\xff\x9d\x01\x6a\x5b\x52\xb7\xfa\x92\x16\xb2\xb6\x54"
"\x82\xc7\x84\x44\x11\x81\x21\xa2\xc7\xfe\xd8\x3d\xb7\x11\x9e\x91\x82\xaa\xd7"
"\xd1\x8c\x70\x63\xe2\xa4\x57\x55\x59\x10\xaf\x9e\x0e\xfc\x76\x34\x7d\x16\x40"
"\x43\x80\x7f\x58\x1e\xe4\xfb\xe4\x2c\xa9\xde\xdc\x1b\x5e\xb2\xa3\xaa\x3d\x2e"
"\xcd\x59\xe7\xee\xe7\x0b\x36\x29\xf2\x2a\xfd\x16\x1d\x87\x73\x53\xdd\xb9\x9a"
"\xdc\x8e\x07\x00\x6e\x56\xf8\x50\xce";
static uint8_t server_fp_reply4[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x03\x90\x01\xe1\x72\x7e"
"\x0f\x57\xf9\xf5\x88\x0d\xb1\x04\xa6\x25\x7a\x23\xf5\xcf\xff\x1a\xbb\xe1\xe9"
"\x30\x45\x25\x1a\xfb\x97\xeb\x9f\xc0\x01\x1e\xbe\x0f\x3a\x81\xdf\x5b\x69\x1d"
"\x76\xac\xb2\xf7\xa5\xc7\x08\xe3\xd3\x28\xf5\x6b\xb3\x9d\xbd\xe5\xf2\x9c\x8a"
"\x17\xf4\x81\x48\x7e\x3a\xe8\x63\xc6\x78\x32\x54\x22\xe6\xf7\x8e\x16\x6d\x18"
"\xaa\x7f\xd6\x36\x25\x8b\xce\x28\x72\x6f\x66\x1f\x73\x88\x93\xce\x44\x31\x1e"
"\x4b\xe6\xc0\x53\x51\x93\xe5\xef\x72\xe8\x68\x62\x33\x72\x9c\x22\x7d\x82\x0c"
"\x99\x94\x45\xd8\x92\x46\xc8\xc3\x59";
static uint8_t server_fp_header[] = "\x46\x50\x4c\x59\x03\x01\x04\x00\x00\x00\x00\x14";
resp->respcode = 200; // assume it's handled
// uint8_t *out;
// size_t out_len;
int version_pos = 4;
int mode_pos = 14;
int type_pos = 5;
int seq_pos = 6;
int setup_message_type = 1;
int setup1_message_seq = 1;
int setup2_message_seq = 3;
int setup2_suffix_len = 20;
// int ret;
// response and len are dummy values and can be ignored
// debug(1, "Version: %02x, mode: %02x, type: %02x, seq: %02x", req->content[version_pos],
// req->content[mode_pos], req->content[type_pos], req->content[seq_pos]);
if (req->content[version_pos] != 3 || req->content[type_pos] != setup_message_type) {
debug(1, "Unsupported FP version.");
}
char *response = NULL;
size_t len = 0;
if (req->content[seq_pos] == setup1_message_seq) {
// All replies are the same length. -1 to account for the NUL byte at the end.
len = sizeof(server_fp_reply1) - 1;
if (req->content[mode_pos] == 0)
response = memdup(server_fp_reply1, len);
if (req->content[mode_pos] == 1)
response = memdup(server_fp_reply2, len);
if (req->content[mode_pos] == 2)
response = memdup(server_fp_reply3, len);
if (req->content[mode_pos] == 3)
response = memdup(server_fp_reply4, len);
} else if (req->content[seq_pos] == setup2_message_seq) {
// -1 to account for the NUL byte at the end.
len = sizeof(server_fp_header) - 1 + setup2_suffix_len;
response = malloc(len);
if (response) {
memcpy(response, server_fp_header, sizeof(server_fp_header) - 1);
memcpy(response + sizeof(server_fp_header) - 1,
req->content + req->contentlength - setup2_suffix_len, setup2_suffix_len);
}
}
if (response == NULL) {
debug(1, "Cannot create a response.");
}
resp->content = response; // these will be freed when the data is sent
resp->contentlength = len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
}
/*
<key>Identifier</key>
<string>21cc689d-d5de-4814-872c-71d1426b57e0</string>
<key>Enable_HK_Access_Control</key>
<true/>
<key>PublicKey</key>
<data>
qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
</data>
<key>Device_Name</key>
<string>MyDevice</string>
<key>Access_Control_Level</key>
<integer>0</integer>
*/
void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
uint8_t public_key[32];
pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id);
plist_t response_plist = plist_new_dict();
plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
plist_dict_set_item(response_plist, "PublicKey",
plist_new_data((const char *)public_key, sizeof(public_key)));
plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
debug_log_rtsp_message(2, "POST /configure response:", resp);
}
void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, NULL, req);
if (conn->airplay_stream_category == remote_control_stream) {
plist_t array_plist = plist_new_array();
plist_t response_plist = plist_new_dict();
plist_dict_set_item(response_plist, "streams", array_plist);
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
debug_log_rtsp_message(2, "FEEDBACK response (remote_control_stream):", resp);
}
/* not finished yet
plist_t payload_plist = plist_new_dict();
plist_dict_set_item(payload_plist, "type", plist_new_uint(103));
plist_dict_set_item(payload_plist, "sr", plist_new_real(44100.0));
plist_t array_plist = plist_new_array();
plist_array_append_item(array_plist, payload_plist);
plist_t response_plist = plist_new_dict();
plist_dict_set_item(response_plist, "streams",array_plist);
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
// plist_free(array_plist);
// plist_free(payload_plist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
debug_log_rtsp_message(2, "FEEDBACK response:", resp);
*/
}
void handle_command(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, NULL, req);
if (rtsp_message_contains_plist(req)) {
plist_t command_dict = NULL;
plist_from_memory(req->content, req->contentlength, &command_dict);
if (command_dict != NULL) {
// we have a plist -- try to get the dict item keyed to "updateMRSupportedCommands"
plist_t item = plist_dict_get_item(command_dict, "type");
if (item != NULL) {
char *typeValue = NULL;
plist_get_string_val(item, &typeValue);
if ((typeValue != NULL) && (strcmp(typeValue, "updateMRSupportedCommands") == 0)) {
item = plist_dict_get_item(command_dict, "params");
if (item != NULL) {
// the item should be a dict
plist_t item_array = plist_dict_get_item(item, "mrSupportedCommandsFromSender");
if (item_array != NULL) {
// here we have an array of data items
uint32_t items = plist_array_get_size(item_array);
if (items) {
uint32_t item_number;
for (item_number = 0; item_number < items; item_number++) {
plist_t the_item = plist_array_get_item(item_array, item_number);
char *buff = NULL;
uint64_t length = 0;
plist_get_data_val(the_item, &buff, &length);
// debug(1,"Item %d, length: %" PRId64 " bytes", item_number, length);
if ((buff != NULL) && (length >= strlen("bplist00")) &&
(strstr(buff, "bplist00") == buff)) {
// debug(1,"Contains a plist.");
plist_t subsidiary_plist = NULL;
plist_from_memory(buff, length, &subsidiary_plist);
if (subsidiary_plist) {
char *printable_plist = plist_content(subsidiary_plist);
if (printable_plist) {
debug(3, "\n%s", printable_plist);
free(printable_plist);
} else {
debug(1, "Can't print the plist!");
}
// plist_free(subsidiary_plist);
} else {
debug(1, "Can't access the plist!");
}
}
if (buff != NULL)
free(buff);
}
}
} else {
debug(1, "POST /command no mrSupportedCommandsFromSender item.");
}
} else {
debug(1, "POST /command no params dict.");
}
resp->respcode = 400; // say it's a bad request
} else {
debug(1,
"POST /command plist type is \"%s\", but \"updateMRSupportedCommands\" expected.",
typeValue);
}
if (typeValue != NULL)
free(typeValue);
} else {
debug(2, "Could not find a \"type\" item.");
}
plist_free(command_dict);
} else {
debug(1, "POST /command plist cannot be inputted.");
}
} else {
debug(1, "POST /command contains no plist");
}
}
void handle_audio_mode(rtsp_conn_info *conn, rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, NULL, req);
}
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
if (strcmp(req->path, "/pair-setup") == 0) {
handle_pair_setup(conn, req, resp);
} else if (strcmp(req->path, "/pair-verify") == 0) {
handle_pair_verify(conn, req, resp);
} else if (strcmp(req->path, "/pair-add") == 0) {
handle_pair_add(conn, req, resp);
} else if (strcmp(req->path, "/pair-remove") == 0) {
handle_pair_remove(conn, req, resp);
} else if (strcmp(req->path, "/pair-list") == 0) {
handle_pair_list(conn, req, resp);
} else if (strcmp(req->path, "/fp-setup") == 0) {
handle_fp_setup(conn, req, resp);
} else if (strcmp(req->path, "/configure") == 0) {
handle_configure(conn, req, resp);
} else if (strcmp(req->path, "/feedback") == 0) {
handle_feedback(conn, req, resp);
} else if (strcmp(req->path, "/command") == 0) {
handle_command(conn, req, resp);
} else if (strcmp(req->path, "/audioMode") == 0) {
handle_audio_mode(conn, req, resp);
} else {
debug(1, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number,
req->path, req->contentlength);
debug_log_rtsp_message(2, "POST request", req);
}
}
void handle_setpeers(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(2, "Connection %d: SETPEERS %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, "SETPEERS request", req);
/*
char timing_list_message[4096];
timing_list_message[0] = 'T';
timing_list_message[1] = 0;
// ensure the client itself is first -- it's okay if it's duplicated later
strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 -
strlen(timing_list_message)); strncat(timing_list_message, (const char
*)&conn->client_ip_string, sizeof(timing_list_message) - 1 - strlen(timing_list_message));
plist_t addresses_array = NULL;
plist_from_memory(req->content, req->contentlength, &addresses_array);
uint32_t items = plist_array_get_size(addresses_array);
if (items) {
uint32_t item;
for (item = 0; item < items; item++) {
plist_t n = plist_array_get_item(addresses_array, item);
char *ip_address = NULL;
plist_get_string_val(n, &ip_address);
// debug(1,ip_address);
strncat(timing_list_message, " ",
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
strncat(timing_list_message, ip_address,
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
if (ip_address != NULL)
free(ip_address);
}
ptp_send_control_message_string(timing_list_message);
}
plist_free(addresses_array);
*/
// set_client_as_ptp_clock(conn);
resp->respcode = 200;
}
#endif
#ifndef CONFIG_AIRPLAY_2
void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(2, "OPTIONS request", req);
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
msg_add_header(resp, "Public",
"ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
#endif
#ifdef CONFIG_AIRPLAY_2
void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(2, "OPTIONS request", req);
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
msg_add_header(resp, "Public",
"ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, FLUSHBUFFERED, TEARDOWN, "
"OPTIONS, POST, GET, PUT");
}
void teardown_phase_one(rtsp_conn_info *conn) {
// this can be called more than once on the same connection --
// by the player itself but also by the play session being killed
if (conn->player_thread) {
player_stop(conn); // this nulls the player_thread
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
}
if (conn->session_key) {
free(conn->session_key);
conn->session_key = NULL;
}
}
void teardown_phase_two(rtsp_conn_info *conn) {
// we are being asked to disconnect
// this can be called more than once on the same connection --
// by the player itself but also by the play seesion being killed
debug(2, "Connection %d: TEARDOWN %s connection.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
if (conn->airplay_stream_category == remote_control_stream) {
if (conn->rtp_data_thread) {
debug(2, "Connection %d: TEARDOWN %s Delete Data Thread.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
pthread_cancel(*conn->rtp_data_thread);
pthread_join(*conn->rtp_data_thread, NULL);
free(conn->rtp_data_thread);
conn->rtp_data_thread = NULL;
}
if (conn->data_socket) {
debug(2, "Connection %d: TEARDOWN %s Close Data Socket.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
close(conn->data_socket);
conn->data_socket = 0;
}
}
if (conn->rtp_event_thread) {
debug(2, "Connection %d: TEARDOWN %s Delete Event Thread.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
pthread_cancel(*conn->rtp_event_thread);
pthread_join(*conn->rtp_event_thread, NULL);
free(conn->rtp_event_thread);
conn->rtp_event_thread = NULL;
}
if (conn->event_socket) {
debug(2, "Connection %d: TEARDOWN %s Close Event Socket.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
close(conn->event_socket);
conn->event_socket = 0;
}
// if we are closing a PTP stream only, do this
if (conn->airplay_stream_category == ptp_stream) {
if (conn->airplay_gid != NULL) {
free(conn->airplay_gid);
conn->airplay_gid = NULL;
#ifdef CONFIG_METADATA
// this is here to ensure it's only performed once during a teardown of a ptp stream
send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1);
#endif
}
conn->groupContainsGroupLeader = 0;
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
build_bonjour_strings(conn);
debug(2, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
mdns_update(NULL, secondary_txt_records);
if (conn->dacp_active_remote != NULL) {
free(conn->dacp_active_remote);
conn->dacp_active_remote = NULL;
}
release_play_lock(conn);
clear_ptp_clock();
}
}
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(2, "TEARDOWN: ", req);
// if (have_player(conn)) {
resp->respcode = 200;
msg_add_header(resp, "Connection", "close");
plist_t messagePlist = plist_from_rtsp_content(req);
if (messagePlist != NULL) {
// now see if the incoming plist contains a "streams" array
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams) {
debug(2, "Connection %d: TEARDOWN %s Close the stream.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
// we are being asked to close a stream
teardown_phase_one(conn);
plist_free(streams);
debug(2, "Connection %d: TEARDOWN %s Close the stream complete", conn->connection_number,
get_category_string(conn->airplay_stream_category));
} else {
debug(2, "Connection %d: TEARDOWN %s Close the connection.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
teardown_phase_one(conn); // try to do phase one anyway
teardown_phase_two(conn);
debug(2, "Connection %d: TEARDOWN %s Close the connection complete", conn->connection_number,
get_category_string(conn->airplay_stream_category));
}
//} else {
// warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)",
// conn->connection_number);
// resp->respcode = 451;
plist_free(messagePlist);
resp->respcode = 200;
} else {
debug(1, "Connection %d: missing plist!", conn->connection_number);
resp->respcode = 451; // don't know what to do here
}
// debug(1,"Bogus exit for valgrind -- remember to comment it out!.");
// exit(EXIT_SUCCESS); //
}
#endif
void teardown(rtsp_conn_info *conn) {
player_stop(conn);
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
if (conn->dacp_active_remote != NULL) {
free(conn->dacp_active_remote);
conn->dacp_active_remote = NULL;
}
}
void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
debug_log_rtsp_message(2, "TEARDOWN request", req);
debug(2, "Connection %d: TEARDOWN", conn->connection_number);
if (have_play_lock(conn)) {
debug(
3,
"TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).",
conn->connection_number);
teardown(conn);
release_play_lock(conn);
resp->respcode = 200;
msg_add_header(resp, "Connection", "close");
debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.",
conn->connection_number);
} else {
warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)",
conn->connection_number);
resp->respcode = 451;
}
// debug(1,"Bogus exit for valgrind -- remember to comment it out!.");
// exit(EXIT_SUCCESS);
}
void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug_log_rtsp_message(2, "FLUSH request", req);
debug(3, "Connection %d: FLUSH", conn->connection_number);
char *p = NULL;
uint32_t rtptime = 0;
char *hdr = msg_get_header(req, "RTP-Info");
if (hdr) {
// debug(1,"FLUSH message received: \"%s\".",hdr);
// get the rtp timestamp
p = strstr(hdr, "rtptime=");
if (p) {
p = strchr(p, '=');
if (p)
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
}
}
debug(2, "RTSP Flush Requested: %u.", rtptime);
if (have_play_lock(conn)) {
#ifdef CONFIG_METADATA
if (p)
send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
else
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
#endif
player_flush(rtptime, conn); // will not crash even it there is no player thread.
resp->respcode = 200;
} else {
warn("Connection %d FLUSH %u received without having the player", conn->connection_number,
rtptime);
resp->respcode = 451;
}
}
#ifdef CONFIG_AIRPLAY_2
#ifdef CONFIG_METADATA
static void check_and_send_plist_metadata(plist_t messagePlist, const char *plist_key,
uint32_t metadata_code) {
plist_t item = plist_dict_get_item(messagePlist, plist_key);
if (item) {
char *value;
plist_get_string_val(item, &value);
if (value != NULL) {
send_metadata('ssnc', metadata_code, value, strlen(value), NULL, 0);
free(value);
}
}
}
#endif
void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int err;
debug(2, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
// debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req);
plist_t messagePlist = plist_from_rtsp_content(req);
plist_t setupResponsePlist = plist_new_dict();
resp->respcode = 400;
// see if we can get a name for the client
char *clientNameString = NULL;
plist_t nameItem = plist_dict_get_item(messagePlist, "name");
if (nameItem != NULL) {
plist_get_string_val(nameItem, &clientNameString);
} else {
clientNameString = strdup("<unknown>");
}
// see if the incoming plist contains a "streams" array
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams == NULL) {
// no "streams" plist, so it must (?) be an initial setup
debug(2,
"Connection %d SETUP: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
conn->airplay_stream_category = unspecified_stream_category;
// figure out what category of stream it is, by looking at the plist
plist_t timingProtocol = plist_dict_get_item(messagePlist, "timingProtocol");
if (timingProtocol != NULL) {
char *timingProtocolString = NULL;
plist_get_string_val(timingProtocol, &timingProtocolString);
if (timingProtocolString) {
if (strcmp(timingProtocolString, "PTP") == 0) {
debug(1, "Connection %d: AP2 PTP connection from %s:%u (\"%s\") to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
clientNameString, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = ptp_stream;
conn->timing_type = ts_ptp;
#ifdef CONFIG_METADATA
send_ssnc_metadata('conn', conn->client_ip_string, strlen(conn->client_ip_string),
1); // before disconnecting an existing play
#endif
get_play_lock(conn, 1); // airplay 2 always allows interruption
#ifdef CONFIG_METADATA
send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1);
send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1);
#endif
} else if (strcmp(timingProtocolString, "NTP") == 0) {
debug(1, "Connection %d: SETUP: NTP setup from %s:%u (\"%s\") to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
clientNameString, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = ntp_stream;
conn->timing_type = ts_ntp;
} else if (strcmp(timingProtocolString, "None") == 0) {
debug(3,
"Connection %d: SETUP: a \"None\" setup detected from %s:%u (\"%s\") to self at "
"%s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
clientNameString, conn->self_ip_string, conn->self_rtsp_port);
// now check to see if it's got the "isRemoteControlOnly" item and check it's true
plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly");
if (isRemoteControlOnly != NULL) {
uint8_t isRemoteControlOnlyBoolean = 0;
plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean);
if (isRemoteControlOnlyBoolean != 0) {
debug(
1,
"Connection %d: Remote Control connection from %s:%u (\"%s\") to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
clientNameString, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = remote_control_stream;
} else {
debug(1,
"Connection %d: SETUP: a \"None\" setup detected, with "
"\"isRemoteControlOnly\" item set to \"false\".",
conn->connection_number);
}
} else {
debug(1,
"Connection %d: SETUP: a \"None\" setup detected, but no "
"\"isRemoteControlOnly\" item detected.",
conn->connection_number);
}
}
// here, we know it's an initial setup and we know the kind of setup being requested
// if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and
// timingPeerList
if (conn->airplay_stream_category == ptp_stream) {
if (ptp_shm_interface_open() !=
0) // it should be open already, but just in case it isn't...
die("Can not access the NQPTP service. Has it stopped running?");
// clear_ptp_clock();
debug_log_rtsp_message(2, "SETUP \"PTP\" message", req);
plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID");
if (groupUUID) {
char *gid = NULL;
plist_get_string_val(groupUUID, &gid);
if (gid) {
if (conn->airplay_gid)
free(conn->airplay_gid);
conn->airplay_gid = gid; // it'll be free'd later on...
} else {
debug(1, "Invalid groupUUID");
}
} else {
debug(1, "No groupUUID in SETUP");
}
// now see if the group contains a group leader
plist_t groupContainsGroupLeader =
plist_dict_get_item(messagePlist, "groupContainsGroupLeader");
if (groupContainsGroupLeader) {
uint8_t value = 0;
plist_get_bool_val(groupContainsGroupLeader, &value);
conn->groupContainsGroupLeader = value;
debug(2, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
} else {
debug(1, "No groupContainsGroupLeader in SETUP");
}
char timing_list_message[4096];
timing_list_message[0] = 'T';
timing_list_message[1] = 0;
// ensure the client itself is first -- it's okay if it's duplicated later
strncat(timing_list_message, " ",
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
strncat(timing_list_message, (const char *)&conn->client_ip_string,
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo");
if (timing_peer_info) {
// first, get the incoming plist.
plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses");
if (addresses_array) {
// iterate through the array of items
uint32_t items = plist_array_get_size(addresses_array);
if (items) {
uint32_t item;
for (item = 0; item < items; item++) {
plist_t n = plist_array_get_item(addresses_array, item);
char *ip_address = NULL;
plist_get_string_val(n, &ip_address);
// debug(1, "Timing peer: %s", ip_address);
strncat(timing_list_message, " ",
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
strncat(timing_list_message, ip_address,
sizeof(timing_list_message) - 1 - strlen(timing_list_message));
free(ip_address);
}
} else {
debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.",
conn->connection_number);
}
} else {
debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses",
conn->connection_number);
}
// make up the timing peer info list part of the response...
// debug(1,"Create timingPeerInfoPlist");
plist_t timingPeerInfoPlist = plist_new_dict();
plist_t addresses = plist_new_array(); // to hold the device's interfaces
plist_array_append_item(addresses, plist_new_string(conn->self_ip_string));
// debug(1,"self ip: \"%s\"", conn->self_ip_string);
struct ifaddrs *addrs, *iap;
getifaddrs(&addrs);
for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
// debug(1, "Interface index %d, name: \"%s\"",if_nametoindex(iap->ifa_name),
// iap->ifa_name);
if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) &&
((iap->ifa_flags & IFF_LOOPBACK) == 0)) {
char buf[INET6_ADDRSTRLEN + 1]; // +1 for a NUL
memset(buf, 0, sizeof(buf));
if (iap->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr);
inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf));
plist_array_append_item(addresses, plist_new_string(buf));
// debug(1, "Own address IPv6: %s", buf);
// strncat(timing_list_message, " ",
// sizeof(timing_list_message) - 1 - strlen(timing_list_message));
// strncat(timing_list_message, buf,
// sizeof(timing_list_message) - 1 - strlen(timing_list_message));
} else {
struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr);
inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf));
plist_array_append_item(addresses, plist_new_string(buf));
// debug(1, "Own address IPv4: %s", buf);
// strncat(timing_list_message, " ",
// sizeof(timing_list_message) - 1 - strlen(timing_list_message));
// strncat(timing_list_message, buf,
// sizeof(timing_list_message) - 1 - strlen(timing_list_message));
}
}
}
freeifaddrs(addrs);
// debug(1,"initial timing peer command: \"%s\".", timing_list_message);
// ptp_send_control_message_string(timing_list_message);
set_client_as_ptp_clock(conn);
plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses);
plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string));
plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist);
// get a port to use as an event port
// bind a new TCP port and get a socket
conn->local_event_port = 0; // any port
int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family,
conn->self_ip_string, conn->self_scope_id,
&conn->local_event_port, &conn->event_socket);
if (err) {
die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event "
"port",
conn->connection_number, err);
}
debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number,
conn->local_event_port);
if (conn->rtp_event_thread != NULL)
debug(1, "previous rtp_event_thread allocation not freed, it seems.");
conn->rtp_event_thread = malloc(sizeof(pthread_t));
if (conn->rtp_event_thread == NULL)
die("Couldn't allocate space for pthread_t");
pthread_create(conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
cancel_all_RTSP_threads(unspecified_stream_category,
conn->connection_number); // kill all the other listeners
config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
build_bonjour_strings(conn);
debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
mdns_update(NULL, secondary_txt_records);
resp->respcode = 200;
} else {
debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.",
conn->connection_number);
}
#ifdef CONFIG_METADATA
check_and_send_plist_metadata(messagePlist, "name", 'snam');
check_and_send_plist_metadata(messagePlist, "deviceID", 'cdid');
check_and_send_plist_metadata(messagePlist, "model", 'cmod');
check_and_send_plist_metadata(messagePlist, "macAddress", 'cmac');
#endif
} else if (conn->airplay_stream_category == ntp_stream) {
debug(1, "SETUP on Connection %d: ntp stream handling is not implemented!",
conn->connection_number, req);
warn("Shairport Sync can not handle NTP streams.");
} else if (conn->airplay_stream_category == remote_control_stream) {
debug_log_rtsp_message(2, "SETUP (no stream) \"isRemoteControlOnly\" message", req);
// get a port to use as an event port
// bind a new TCP port and get a socket
conn->local_event_port = 0; // any port
int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family,
conn->self_ip_string, conn->self_scope_id,
&conn->local_event_port, &conn->event_socket);
if (err) {
die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event "
"port",
conn->connection_number, err);
}
debug(2, "Connection %d SETUP (RC): TCP Remote Control event port opened: %u.",
conn->connection_number, conn->local_event_port);
if (conn->rtp_event_thread != NULL)
debug(1,
"Connection %d SETUP (RC): previous rtp_event_thread allocation not freed, it "
"seems.",
conn->connection_number);
conn->rtp_event_thread = malloc(sizeof(pthread_t));
if (conn->rtp_event_thread == NULL)
die("Couldn't allocate space for pthread_t");
pthread_create(conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0));
cancel_all_RTSP_threads(
remote_control_stream,
conn->connection_number); // kill all the other remote control listeners
resp->respcode = 200;
} else {
debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.",
conn->connection_number, timingProtocolString);
warn("Shairport Sync can not handle streams of this type: \"%s\".", timingProtocolString);
}
free(timingProtocolString);
} else {
debug(1, "SETUP on Connection %d: Can't retrieve timingProtocol string in initial SETUP.",
conn->connection_number);
}
} else {
debug(1,
"SETUP on Connection %d: Unrecognised SETUP incoming message from \"%s\": no "
"timingProtocol or streams plist found.",
conn->connection_number, (const char *)conn->client_ip_string);
debug_log_rtsp_message(2, "Unrecognised SETUP incoming message.", req);
warn("Unrecognised SETUP incoming message -- ignored.");
}
} else {
debug(2, "Connection %d: SETUP on %s. A \"streams\" array has been found",
conn->connection_number, get_category_string(conn->airplay_stream_category));
if (conn->airplay_stream_category == ptp_stream) {
// get stream[0]
plist_t stream0 = plist_array_get_item(streams, 0);
plist_t streams_array = plist_new_array(); // to hold the ports and stuff
plist_t stream0dict = plist_new_dict();
// get the session key -- it must have one
plist_t item = plist_dict_get_item(stream0, "shk"); // session key
uint64_t item_value = 0; // the length
plist_get_data_val(item, (char **)&conn->session_key, &item_value);
// more stuff
// set up a UDP control stream and thread and a UDP or TCP audio stream and thread
// bind a new UDP port and get a socket
conn->local_ap2_control_port = 0; // any port
err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string,
conn->self_scope_id, &conn->local_ap2_control_port,
&conn->ap2_control_socket);
if (err) {
die("Error %d: could not find a UDP port to use as an ap2_control port", err);
}
debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number,
conn->local_ap2_control_port);
pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn);
// get the DACP-ID and Active Remote for remote control stuff
char *ar = msg_get_header(req, "Active-Remote");
if (ar) {
debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".",
conn->connection_number, ar);
// get the active remote
if (conn->dacp_active_remote) // this is in case SETUP was previously called
free(conn->dacp_active_remote);
conn->dacp_active_remote = strdup(ar);
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1);
#endif
} else {
debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.",
conn->connection_number);
if (conn->dacp_active_remote) { // this is in case SETUP was previously called
free(conn->dacp_active_remote);
conn->dacp_active_remote = NULL;
}
}
ar = msg_get_header(req, "DACP-ID");
if (ar) {
debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".",
conn->connection_number, ar);
if (conn->dacp_id) // this is in case SETUP was previously called
free(conn->dacp_id);
conn->dacp_id = strdup(ar);
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1);
#endif
} else {
debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.",
conn->connection_number);
if (conn->dacp_id) { // this is in case SETUP was previously called
free(conn->dacp_id);
conn->dacp_id = NULL;
}
}
// now, get the type of the stream.
item = plist_dict_get_item(stream0, "type");
item_value = 0;
plist_get_uint_val(item, &item_value);
switch (item_value) {
case 96: {
debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number);
debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req);
// get_play_lock(conn);
conn->airplay_stream_type = realtime_stream;
// bind a new UDP port and get a socket
conn->local_realtime_audio_port = 0; // any port
err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string,
conn->self_scope_id, &conn->local_realtime_audio_port,
&conn->realtime_audio_socket);
if (err) {
die("Error %d: could not find a UDP port to use as a realtime_audio port", err);
}
debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number,
conn->local_realtime_audio_port);
pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver,
(void *)conn);
plist_dict_set_item(stream0dict, "type", plist_new_uint(96));
plist_dict_set_item(stream0dict, "dataPort",
plist_new_uint(conn->local_realtime_audio_port));
conn->stream.type = ast_apple_lossless;
debug(3, "An ALAC stream has been detected.");
// Set reasonable connection defaults
conn->stream.fmtp[0] = 96;
conn->stream.fmtp[1] = 352;
conn->stream.fmtp[2] = 0;
conn->stream.fmtp[3] = 16;
conn->stream.fmtp[4] = 40;
conn->stream.fmtp[5] = 10;
conn->stream.fmtp[6] = 14;
conn->stream.fmtp[7] = 2;
conn->stream.fmtp[8] = 255;
conn->stream.fmtp[9] = 0;
conn->stream.fmtp[10] = 0;
conn->stream.fmtp[11] = 44100;
// set the parameters of the player (as distinct from the parameters of the decoder --
// that's done later).
conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet.
conn->input_rate = conn->stream.fmtp[11];
conn->input_num_channels = conn->stream.fmtp[7];
conn->input_bit_depth = conn->stream.fmtp[3];
conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
debug(2, "Realtime Stream Play");
activity_monitor_signify_activity(1);
player_prepare_to_play(conn);
player_play(conn);
conn->rtp_running = 1; // hack!
} break;
case 103: {
debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number);
debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req);
// get_play_lock(conn);
conn->airplay_stream_type = buffered_stream;
// get needed stuff
// bind a new TCP port and get a socket
conn->local_buffered_audio_port = 0; // any port
err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
conn->self_scope_id, &conn->local_buffered_audio_port,
&conn->buffered_audio_socket);
if (err) {
die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a "
"buffered_audio port",
conn->connection_number, err);
}
debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number,
conn->local_buffered_audio_port);
// hack.
conn->max_frames_per_packet = 352; // number of audio frames per packet.
conn->input_rate = 44100; // we are stuck with this for the moment.
conn->input_num_channels = 2;
conn->input_bit_depth = 16;
conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
activity_monitor_signify_activity(1);
player_prepare_to_play(
conn); // get capabilities of DAC before creating the buffered audio thread
pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor,
(void *)conn);
plist_dict_set_item(stream0dict, "type", plist_new_uint(103));
plist_dict_set_item(stream0dict, "dataPort",
plist_new_uint(conn->local_buffered_audio_port));
plist_dict_set_item(stream0dict, "audioBufferSize",
plist_new_uint(conn->ap2_audio_buffer_size));
// this should be cancelled by an activity_monitor_signify_activity(1)
// call in the SETRATEANCHORI handler, which should come up right away
activity_monitor_signify_activity(0);
player_play(conn);
conn->rtp_running = 1; // hack!
} break;
default:
debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".",
conn->connection_number, item_value);
debug_log_rtsp_message(1, "Unhandled stream type incoming message", req);
}
plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port));
plist_array_append_item(streams_array, stream0dict);
plist_dict_set_item(setupResponsePlist, "streams", streams_array);
resp->respcode = 200;
} else if (conn->airplay_stream_category == remote_control_stream) {
debug(2, "Connection %d (RC): SETUP: Remote Control Stream received from %s.",
conn->connection_number, conn->client_ip_string);
debug_log_rtsp_message(2, "Remote Control Stream SETUP incoming message", req);
/*
// get a port to use as an data port
// bind a new TCP port and get a socket
conn->local_data_port = 0; // any port
int err =
bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
conn->self_scope_id, &conn->local_data_port, &conn->data_socket);
if (err) {
die("SETUP on Connection %d (RC): Error %d: could not find a TCP port to use as a data "
"port",
conn->connection_number, err);
}
debug(1, "Connection %d SETUP (RC): TCP Remote Control data port opened: %u.",
conn->connection_number, conn->local_data_port);
if (conn->rtp_data_thread != NULL)
debug(1, "Connection %d SETUP (RC): previous rtp_data_thread allocation not freed, it
seems.", conn->connection_number); conn->rtp_data_thread = malloc(sizeof(pthread_t)); if
(conn->rtp_data_thread == NULL) die("Couldn't allocate space for pthread_t");
pthread_create(conn->rtp_data_thread, NULL, &rtp_data_receiver, (void *)conn);
plist_t coreResponseDict = plist_new_dict();
plist_dict_set_item(coreResponseDict, "streamID", plist_new_uint(1));
plist_dict_set_item(coreResponseDict, "type", plist_new_uint(130));
plist_dict_set_item(coreResponseDict, "dataPort", plist_new_uint(conn->local_data_port));
plist_t coreResponseArray = plist_new_array();
plist_array_append_item(coreResponseArray, coreResponseDict);
plist_dict_set_item(setupResponsePlist, "streams", coreResponseArray);
*/
resp->respcode = 200;
} else {
debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.",
conn->connection_number);
}
}
if (resp->respcode == 200) {
plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
plist_free(setupResponsePlist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
}
plist_free(messagePlist);
if (clientNameString != NULL)
free(clientNameString);
debug_log_rtsp_message(2, " SETUP response", resp);
}
#endif
void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: SETUP", conn->connection_number);
resp->respcode = 451; // invalid arguments -- expect them
if (have_play_lock(conn)) {
uint16_t cport, tport;
char *ar = msg_get_header(req, "Active-Remote");
if (ar) {
debug(2, "Connection %d: SETUP: Active-Remote string seen: \"%s\".", conn->connection_number,
ar);
// get the active remote
if (conn->dacp_active_remote) // this is in case SETUP was previously called
free(conn->dacp_active_remote);
conn->dacp_active_remote = strdup(ar);
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1);
#endif
} else {
debug(2, "Connection %d: SETUP: Note: no Active-Remote information seen.",
conn->connection_number);
if (conn->dacp_active_remote) { // this is in case SETUP was previously called
free(conn->dacp_active_remote);
conn->dacp_active_remote = NULL;
}
}
ar = msg_get_header(req, "DACP-ID");
if (ar) {
debug(2, "Connection %d: SETUP: DACP-ID string seen: \"%s\".", conn->connection_number, ar);
if (conn->dacp_id) // this is in case SETUP was previously called
free(conn->dacp_id);
conn->dacp_id = strdup(ar);
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1);
#endif
} else {
debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.",
conn->connection_number);
if (conn->dacp_id) { // this is in case SETUP was previously called
free(conn->dacp_id);
conn->dacp_id = NULL;
}
}
char *hdr = msg_get_header(req, "Transport");
if (hdr) {
char *p;
p = strstr(hdr, "control_port=");
if (p) {
p = strchr(p, '=') + 1;
cport = atoi(p);
p = strstr(hdr, "timing_port=");
if (p) {
p = strchr(p, '=') + 1;
tport = atoi(p);
if (conn->rtp_running) {
if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) {
warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) "
"or "
"timing (old %u, new "
"%u) ports! This is probably fatal!",
conn->connection_number, conn->remote_control_port, cport,
conn->remote_timing_port, tport);
} else {
warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing "
"(%u) "
"ports. This is "
"probably not fatal.",
conn->connection_number, conn->remote_control_port, conn->remote_timing_port);
}
} else {
rtp_setup(&conn->local, &conn->remote, cport, tport, conn);
}
if (conn->local_audio_port != 0) {
char resphdr[256] = "";
snprintf(resphdr, sizeof(resphdr),
"RTP/AVP/"
"UDP;unicast;interleaved=0-1;mode=record;control_port=%d;"
"timing_port=%d;server_"
"port=%d",
conn->local_control_port, conn->local_timing_port, conn->local_audio_port);
msg_add_header(resp, "Transport", resphdr);
msg_add_header(resp, "Session", "1");
resp->respcode = 200; // it all worked out okay
debug(2,
"Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: "
"%d, Timing: %d and Audio: %d.",
conn->connection_number, conn->dacp_id, &conn->client_ip_string,
&conn->self_ip_string, conn->local_control_port, conn->local_timing_port,
conn->local_audio_port);
} else {
debug(1, "Connection %d: SETUP seems to specify a null audio port.",
conn->connection_number);
}
} else {
debug(1, "Connection %d: SETUP doesn't specify a timing_port.", conn->connection_number);
}
} else {
debug(1, "Connection %d: SETUP doesn't specify a control_port.", conn->connection_number);
}
} else {
debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number);
}
if (resp->respcode == 200) {
#ifdef CONFIG_METADATA
send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1);
send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1);
#endif
} else {
debug(1, "Connection %d: SETUP error -- releasing the player lock.", conn->connection_number);
release_play_lock(conn);
}
} else {
warn("Connection %d SETUP received without having the player (no ANNOUNCE?)",
conn->connection_number);
}
}
/*
static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(1, "Connection thread %d: IGNORE", conn->connection_number);
resp->respcode = 200;
}
*/
void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
char *cp = req->content;
int cp_left = req->contentlength;
/*
int k = cp_left;
if (k>max_bytes)
k = max_bytes;
for (i = 0; i < k; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", cp[i]);
debug(1, "handle_set_parameter_parameter: \"%s\".",buf);
*/
char *next;
while (cp_left && cp) {
next = nextline(cp, cp_left);
// note: "next" will return NULL if there is no \r or \n or \r\n at the end of this
// but we are always guaranteed that if cp is not null, it will be pointing to something
// NUL-terminated
if (next)
cp_left -= (next - cp);
else
cp_left = 0;
if (!strncmp(cp, "volume: ", strlen("volume: "))) {
float volume = atof(cp + strlen("volume: "));
debug(2, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number,
volume);
// if we are playing, go ahead and change the volume
#ifdef CONFIG_DBUS_INTERFACE
if (dbus_service_is_running()) {
shairport_sync_set_volume(shairportSyncSkeleton, volume);
} else {
#endif
lock_player();
if (playing_conn == conn) {
player_volume(volume, conn);
}
if (conn != NULL) {
conn->own_airplay_volume = volume;
conn->own_airplay_volume_set = 1;
}
unlock_player();
config.last_access_to_volume_info_time = get_absolute_time_in_ns();
#ifdef CONFIG_DBUS_INTERFACE
}
#endif
} else if (strncmp(cp, "progress: ", strlen("progress: ")) ==
0) { // this can be sent even when metadata is not solicited
#ifdef CONFIG_METADATA
char *progress = cp + strlen("progress: ");
// debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
// second
send_ssnc_metadata('prgr', progress, strlen(progress), 1);
#endif
} else {
debug(1, "Connection %d, unrecognised parameter: \"%s\" (%d)\n", conn->connection_number, cp,
strlen(cp));
}
cp = next;
}
}
#ifdef CONFIG_METADATA
// Metadata is not used by shairport-sync.
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
// the pipe and use the metadata.
// We use two 4-character codes to identify each piece of data and we send the
// data itself, if any,
// in base64 form.
// The first 4-character code, called the "type", is either:
// 'core' for all the regular metadadata coming from iTunes, etc., or
// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync
// itself, such as
// start/end delimiters, etc.
// For 'core' metadata, the second 4-character code is the 4-character metadata
// code coming from
// iTunes etc.
// For 'ssnc' metadata, the second 4-character code is used to distinguish the
// messages.
// Cover art is not tagged in the same way as other metadata, it seems, so is
// sent as an 'ssnc' type
// metadata message with the code 'PICT'
// Here are the 'ssnc' codes defined so far:
// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
// first few bytes to see
// which.
// 'abeg' -- active mode entered. No arguments
// 'aend' -- active mode exited. No arguments
// 'pbeg' -- play stream begin. No arguments
// 'pend' -- play stream end. No arguments
// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
// frame number. It seems that all frames up to but not
// including this frame are to be flushed.
//
// 'prsm' -- play stream resume. No arguments. (deprecated)
// 'paus' -- buffered audio stream paused. No arguments.
// 'pres' -- buffered audio stream resumed. No arguments.
// 'pffr' -- the first frame of a play session has been received and has been validly
// timed.
// 'pvol' -- play volume. The volume is sent as a string --
// "airplay_volume,volume,lowest_volume,highest_volume"
// volume, lowest_volume and highest_volume are given in dB.
// The "airplay_volume" is what's sent to the player, and is from
// 0.00 down to -30.00,
// with -144.00 meaning mute.
// This is linear on the volume control slider of iTunes or iOS
// AirPlay.
// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP
// timestamps for the start
// of the current play sequence, the current play point and the end of the
// play sequence.
// I guess the timestamps wrap at 2^32.
// 'mdst' -- a sequence of metadata is about to start; will have, as data,
// the rtptime associated with the metadata, if available
// 'mden' -- a sequence of metadata has ended; will have, as data, the
// rtptime associated with the metadata, if available
// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime
// associated with the picture, if available
// 'pcen' -- a picture has been sent; will have, as data, the rtptime
// associated with the metadata, if available
// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session.
// Specifically, it's the "X-Apple-Client-Name" string
// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play
// session. Specifically, it's the "User-Agent" string
// The next two two tokens are to facilitate remote control of the source.
// There is some information at http://nto.github.io/AirPlay.html about
// remote control of the source.
//
// 'daid' -- this is the source's DACP-ID (if it has one -- it's not
// guaranteed), useful if you want to remotely control the source. Use this
// string to identify the source's remote control on the network.
// 'acre' -- this is the source's Active-Remote token, necessary if you want
// to send commands to the source's remote control (if it has one).
// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon
// as the client has exclusive access to the player and after any existing
// play session has been interrupted and terminated.
// `conn` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
// It is sent as soon as the client requests access to the player.
// If Shairport Sync is already playing, this message is sent before the current
// play session is stopped.
// `svip` -- the payload is the IP number of the server, i.e. the player itself.
// Can be an IPv4 or an IPv6 number.
// `svna` -- the payload is the service name of the player, i.e. the name by
// which it is seen in the AirPlay menu.
// `disc` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
// It is sent when a client has been disconnected.
// `dapo` -- the payload is the port number (as text) on the server to which remote
// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
// ``
// A special sub-protocol is used for sending large data items over UDP
// If the payload exceeded 4 MB, it is chunked using the following format:
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
// Notice that the number of items is different to the standard
// 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 docoding 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!
//
static int fd = -1;
// static int dirty = 0;
pc_queue metadata_queue;
#define metadata_queue_size 500
metadata_package metadata_queue_items[metadata_queue_size];
pthread_t metadata_thread;
#ifdef CONFIG_METADATA_HUB
pc_queue metadata_hub_queue;
#define metadata_hub_queue_size 500
metadata_package metadata_hub_queue_items[metadata_hub_queue_size];
pthread_t metadata_hub_thread;
#endif
#ifdef CONFIG_MQTT
pc_queue metadata_mqtt_queue;
#define metadata_mqtt_queue_size 500
metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
pthread_t metadata_mqtt_thread;
#endif
static int metadata_sock = -1;
static struct sockaddr_in metadata_sockaddr;
static char *metadata_sockmsg;
pc_queue metadata_multicast_queue;
#define metadata_multicast_queue_size 500
metadata_package metadata_multicast_queue_items[metadata_queue_size];
pthread_t metadata_multicast_thread;
void metadata_create_multicast_socket(void) {
if (config.metadata_enabled == 0)
return;
// Unlike metadata pipe, socket is opened once and stays open,
// so we can call it in create
if (config.metadata_sockaddr && config.metadata_sockport) {
metadata_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (metadata_sock < 0) {
debug(1, "Could not open metadata socket");
} else {
int buffer_size = METADATA_SNDBUF;
setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
metadata_sockaddr.sin_family = AF_INET;
metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
metadata_sockaddr.sin_port = htons(config.metadata_sockport);
metadata_sockmsg = malloc(config.metadata_sockmsglength);
if (metadata_sockmsg) {
memset(metadata_sockmsg, 0, config.metadata_sockmsglength);
} else {
die("Could not malloc metadata multicast socket buffer");
}
}
}
}
void metadata_delete_multicast_socket(void) {
if (config.metadata_enabled == 0)
return;
shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer
close(metadata_sock);
if (metadata_sockmsg)
free(metadata_sockmsg);
}
void metadata_open(void) {
if (config.metadata_enabled == 0)
return;
size_t pl = strlen(config.metadata_pipename) + 1;
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
fd = try_to_open_pipe_for_writing(path);
free(path);
}
static void metadata_close(void) {
if (fd < 0)
return;
close(fd);
fd = -1;
}
void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
// debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code,
// length);
if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
char *ptr = metadata_sockmsg;
uint32_t v;
v = htonl(type);
memcpy(ptr, &v, 4);
ptr += 4;
v = htonl(code);
memcpy(ptr, &v, 4);
ptr += 4;
memcpy(ptr, data, length);
sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
sizeof(metadata_sockaddr));
} else if (metadata_sock >= 0) {
// send metadata in numbered chunks using the protocol:
// ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
uint32_t chunk_ix = 0;
if (config.metadata_sockmsglength == 24)
die("A divide by zero almost occurred (config.metadata_sockmsglength = 24).");
uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
chunk_total++;
}
uint32_t remaining = length;
uint32_t v;
char *data_crsr = data;
do {
char *ptr = metadata_sockmsg;
memcpy(ptr, "ssncchnk", 8);
ptr += 8;
v = htonl(chunk_ix);
memcpy(ptr, &v, 4);
ptr += 4;
v = htonl(chunk_total);
memcpy(ptr, &v, 4);
ptr += 4;
v = htonl(type);
memcpy(ptr, &v, 4);
ptr += 4;
v = htonl(code);
memcpy(ptr, &v, 4);
ptr += 4;
size_t datalen = remaining;
if (datalen > config.metadata_sockmsglength - 24) {
datalen = config.metadata_sockmsglength - 24;
}
memcpy(ptr, data_crsr, datalen);
data_crsr += datalen;
sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
(struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
chunk_ix++;
remaining -= datalen;
if (remaining == 0)
break;
} while (1);
}
}
void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
// debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
int ret = 0;
// readers may go away and come back
if (fd < 0)
metadata_open();
if (fd < 0)
return;
char thestring[1024];
snprintf(thestring, 1024, "<item><type>%x</type><code>%x</code><length>%u</length>", type, code,
length);
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 1",ret);
return;
}
if ((data != NULL) && (length > 0)) {
snprintf(thestring, 1024, "\n<data encoding=\"base64\">\n");
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 2",ret);
return;
}
// here, we write the data in base64 form using our nice base64 encoder
// but, we break it into lines of 76 output characters, except for the last
// one.
// thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time
size_t remaining_count = length;
char *remaining_data = data;
// size_t towrite_count;
char outbuf[76];
while ((remaining_count) && (ret >= 0)) {
size_t towrite_count = remaining_count;
if (towrite_count > 57)
towrite_count = 57;
size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
NULL)
debug(1, "Error encoding base64 data.");
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
// %d.",remaining_count,ret,outbuf_size);
// ret = non_blocking_write(fd, outbuf, outbuf_size);
ret = write(fd, outbuf, outbuf_size);
if (ret < 0) {
// debug(1,"metadata_process error %d exit 3",ret);
return;
}
remaining_data += towrite_count;
remaining_count -= towrite_count;
}
snprintf(thestring, 1024, "</data>");
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 4",ret);
return;
}
}
snprintf(thestring, 1024, "</item>\n");
// ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 5",ret);
return;
}
}
void metadata_pack_cleanup_function(void *arg) {
// debug(1, "metadata_pack_cleanup_function called");
metadata_package *pack = (metadata_package *)arg;
if (pack->carrier)
msg_free(&pack->carrier); // release the message
else if (pack->data)
free(pack->data);
// debug(1, "metadata_pack_cleanup_function exit");
}
void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_thread_cleanup_function called");
metadata_close();
pc_queue_delete(&metadata_queue);
}
void *metadata_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
metadata_queue_size, "pipe");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
if (pack.carrier) {
debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code,
pack.length, pack.carrier->index_number);
} else {
debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
}
metadata_process(pack.type, pack.code, pack.data, pack.length);
debug(3, " pipe: done.");
}
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_multicast_thread_cleanup_function called");
metadata_delete_multicast_socket();
pc_queue_delete(&metadata_multicast_queue);
}
void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_multicast_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
if (pack.carrier) {
debug(3,
" multicast: type "
"%x, code %x, length %u, message %d.",
pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3,
" multicast: type "
"%x, code %x, length %u.",
pack.type, pack.code, pack.length);
}
metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
debug(3,
" multicast: done.");
}
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#ifdef CONFIG_METADATA_HUB
void metadata_hub_close(void) {}
void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_hub_thread_cleanup_function called");
metadata_hub_close();
pc_queue_delete(&metadata_hub_queue);
}
void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
metadata_hub_queue_size, "hub");
metadata_package pack;
pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_hub_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (pack.carrier) {
debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type,
pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code,
pack.length);
}
metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
debug(3, " hub: done.");
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#endif
#ifdef CONFIG_MQTT
void metadata_mqtt_close(void) {}
void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_mqtt_thread_cleanup_function called");
metadata_mqtt_close();
pc_queue_delete(&metadata_mqtt_queue);
// debug(2, "metadata_mqtt_thread_cleanup_function done");
}
void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
metadata_mqtt_queue_size, "mqtt");
metadata_package pack;
pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
while (1) {
pc_queue_get_item(&metadata_mqtt_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.mqtt_enabled) {
if (pack.carrier) {
debug(3,
" mqtt: type %x, code %x, length %u, message "
"%d.",
pack.type, pack.code, pack.length, pack.carrier->index_number);
} else {
debug(3, " mqtt: type %x, code %x, length %u.",
pack.type, pack.code, pack.length);
}
mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
debug(3, " mqtt: done.");
}
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
pthread_exit(NULL);
}
#endif
void metadata_init(void) {
if (config.metadata_enabled) {
// create the metadata pipe, if necessary
size_t pl = strlen(config.metadata_pipename) + 1;
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
mode_t oldumask = umask(000);
if (mkfifo(path, 0666) && errno != EEXIST)
die("Could not create metadata pipe \"%s\".", path);
umask(oldumask);
debug(1, "metadata pipe name is \"%s\".", path);
// try to open it
fd = try_to_open_pipe_for_writing(path);
// 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.
if ((fd == -1) && (errno != ENXIO)) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, path);
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
(char *)errorstring, path);
}
free(path);
if (pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL) != 0)
debug(1, "Failed to create metadata thread!");
if (pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
NULL) != 0)
debug(1, "Failed to create metadata multicast thread!");
}
#ifdef CONFIG_METADATA_HUB
if (pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL) != 0)
debug(1, "Failed to create metadata hub thread!");
#endif
#ifdef CONFIG_MQTT
if (pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL) != 0)
debug(1, "Failed to create metadata mqtt thread!");
#endif
metadata_running = 1;
}
void metadata_stop(void) {
if (metadata_running) {
debug(2, "metadata_stop called.");
#ifdef CONFIG_MQTT
// debug(2, "metadata stop mqtt thread.");
pthread_cancel(metadata_mqtt_thread);
pthread_join(metadata_mqtt_thread, NULL);
// debug(2, "metadata stop mqtt done.");
#endif
#ifdef CONFIG_METADATA_HUB
// debug(2, "metadata stop hub thread.");
pthread_cancel(metadata_hub_thread);
pthread_join(metadata_hub_thread, NULL);
// debug(2, "metadata stop hub done.");
#endif
if (config.metadata_enabled) {
// debug(2, "metadata stop multicast thread.");
if (metadata_multicast_thread) {
pthread_cancel(metadata_multicast_thread);
pthread_join(metadata_multicast_thread, NULL);
// debug(2, "metadata stop multicast done.");
}
if (metadata_thread) {
// debug(2, "metadata stop metadata_thread thread.");
pthread_cancel(metadata_thread);
pthread_join(metadata_thread, NULL);
// debug(2, "metadata_stop finished successfully.");
}
}
}
}
int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *data,
uint32_t length, rtsp_message *carrier, int block) {
// clang-format off
// parameters:
// type,
// code,
// pointer to data or NULL,
// length of data or NULL,
// the rtsp_message or NULL,
// the rtsp_message is sent for 'core' messages, because it contains the data
// and must not be freed until the data has been read.
// So, it is passed to send_metadata to be retained, sent to the thread where metadata
// is processed and released (and probably freed).
// The rtsp_message is also sent for certain non-'core' messages.
// The reading of the parameters is a bit complex:
// If the rtsp_message field is non-null, then it represents an rtsp_message
// and the data pointer is assumed to point to something within it.
// The reference counter of the rtsp_message is incremented here and
// should be decremented by the metadata handler when finished.
// If the reference count reduces to zero, the message will be freed.
// If the rtsp_message is NULL, then if the pointer is non-null then the data it
// points to, of the length specified, is memcpy'd and passed to the metadata
// handler. The handler should free it when done.
// If the rtsp_message is NULL and the pointer is also NULL, nothing further
// is done.
// clang-format on
metadata_package pack;
pack.type = type;
pack.code = code;
pack.length = length;
pack.carrier = carrier;
pack.data = data;
if (pack.carrier) {
msg_retain(pack.carrier);
} else {
if (data)
pack.data = memdup(data, length); // only if it's not a null
}
int rc = pc_queue_add_item(queue, &pack, block);
if (rc != 0) {
if (pack.carrier) {
if (rc == EWOULDBLOCK)
debug(2,
"metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, "
"length %u, message %d.",
queue->name, pack.type, pack.code, pack.data, pack.length,
pack.carrier->index_number);
msg_free(&pack.carrier);
} else {
if (rc == EWOULDBLOCK)
debug(
2,
"metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.",
queue->name, pack.type, pack.code, pack.data, pack.length);
if (pack.data)
free(pack.data);
}
}
return rc;
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
int rc = 0;
if (config.metadata_enabled) {
rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
rc =
send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
}
#ifdef CONFIG_METADATA_HUB
rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
#endif
#ifdef CONFIG_MQTT
rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
#endif
return rc;
}
static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn,
rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
char *cp = req->content;
unsigned int cl = req->contentlength;
unsigned int off = 8;
uint32_t itag, vl;
while (off < cl) {
// pick up the metadata tag as an unsigned longint
memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
itag = ntohl(itag);
off += sizeof(uint32_t);
// pick up the length of the data
memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
vl = ntohl(vl);
off += sizeof(uint32_t);
// pass the data over
if (vl == 0)
send_metadata('core', itag, NULL, 0, NULL, 1);
else
send_metadata('core', itag, (char *)(cp + off), vl, req, 1);
// move on to the next item
off += vl;
}
}
#endif
static void handle_get_parameter(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
// debug(1, "Connection %d: GET_PARAMETER", conn->connection_number);
// debug_print_msg_headers(1,req);
// debug_print_msg_content(1,req);
if ((req->content) && (req->contentlength == strlen("volume\r\n")) &&
strstr(req->content, "volume") == req->content) {
debug(2, "Connection %d: current volume (%.6f) requested", conn->connection_number,
suggested_volume(conn));
char *p = malloc(128); // will be automatically deallocated with the response is deleted
if (p) {
resp->content = p;
resp->contentlength = snprintf(p, 128, "\r\nvolume: %.6f\r\n", suggested_volume(conn));
} else {
debug(1, "Couldn't allocate space for a response.");
}
}
resp->respcode = 200;
}
static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: SET_PARAMETER", conn->connection_number);
// if (!req->contentlength)
// debug(1, "received empty SET_PARAMETER request.");
// debug_print_msg_headers(1,req);
char *ct = msg_get_header(req, "Content-Type");
if (ct) {
// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
#ifdef CONFIG_METADATA
// It seems that the rtptime of the message is used as a kind of an ID that
// can be used
// to link items of metadata, including pictures, that refer to the same
// entity.
// If they refer to the same item, they have the same rtptime.
// So we send the rtptime before and after both the metadata items and the
// picture item
// get the rtptime
char *p = NULL;
char *hdr = msg_get_header(req, "RTP-Info");
if (hdr) {
p = strstr(hdr, "rtptime=");
if (p) {
p = strchr(p, '=');
}
}
// not all items have RTP-time stuff in them, which is okay
if (!strncmp(ct, "application/x-dmap-tagged", 25)) {
debug(3, "received metadata tags in SET_PARAMETER request.");
if (p == NULL)
debug(1, "Missing RTP-Time info for metadata");
if (p)
send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting
else
send_metadata('ssnc', 'mdst', NULL, 0, NULL,
0); // metadata starting, if rtptime is not available
handle_set_parameter_metadata(conn, req, resp);
if (p)
send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending
else
send_metadata('ssnc', 'mden', NULL, 0, NULL,
0); // metadata starting, if rtptime is not available
} else if (!strncmp(ct, "image", 5)) {
// Some server simply ignore the md field from the TXT record. If The
// config says 'please, do not include any cover art', we are polite and
// do not write them to the pipe.
if (config.get_coverart) {
// debug(1, "received image in SET_PARAMETER request.");
// note: the image/type tag isn't reliable, so it's not being sent
// -- best look at the first few bytes of the image
if (p == NULL)
debug(1, "Missing RTP-Time info for picture item");
if (p)
send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting
else
send_metadata('ssnc', 'pcst', NULL, 0, NULL,
0); // picture starting, if rtptime is not available
send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1);
if (p)
send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending
else
send_metadata('ssnc', 'pcen', NULL, 0, NULL,
0); // picture ending, if rtptime is not available
} else {
debug(1, "Ignore received picture item (include_cover_art = no).");
}
} else
#endif
if (!strncmp(ct, "text/parameters", 15)) {
// debug(2, "received parameters in SET_PARAMETER request.");
handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress
} else {
debug(1, "Connection %d: received unknown Content-Type \"%s\" in SET_PARAMETER request.",
conn->connection_number, ct);
debug_print_msg_headers(1, req);
}
} else {
debug(1, "Connection %d: missing Content-Type header in SET_PARAMETER request.",
conn->connection_number);
}
resp->respcode = 200;
}
static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(3, "Connection %d: ANNOUNCE", conn->connection_number);
int get_play_status = get_play_lock(conn, config.allow_session_interruption);
if (get_play_status != -1) {
debug(3, "Connection %d: ANNOUNCE has acquired play lock.", conn->connection_number);
// now, if this new session did not break in, then it's okay to reset the next UDP ports
// to the start of the range
if (get_play_status == 1) { // will be zero if it wasn't waiting to break in
resetFreeUDPPort();
}
/*
{
char *cp = req->content;
int cp_left = req->contentlength;
while (cp_left > 1) {
if (strlen(cp) != 0)
debug(1,">>>>>> %s", cp);
cp += strlen(cp) + 1;
cp_left -= strlen(cp) + 1;
}
}
*/
// In AirPlay 2, an ANNOUNCE signifies the start of an AirPlay 1 session.
#ifdef CONFIG_AIRPLAY_2
conn->airplay_type = ap_1;
conn->timing_type = ts_ntp;
debug(1, "Connection %d: Classic AirPlay connection from %s:%u to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
#endif
conn->stream.type = ast_unknown;
resp->respcode = 456; // 456 - Header Field Not Valid for Resource
char *pssid = NULL;
char *paesiv = NULL;
char *prsaaeskey = NULL;
char *pfmtp = NULL;
char *pminlatency = NULL;
char *pmaxlatency = NULL;
// char *pAudioMediaInfo = NULL;
char *pUncompressedCDAudio = NULL;
char *cp = req->content;
int cp_left = req->contentlength;
char *next;
while (cp_left && cp) {
next = nextline(cp, cp_left);
cp_left -= next - cp;
if (!strncmp(cp, "a=rtpmap:96 L16/44100/2", strlen("a=rtpmap:96 L16/44100/2")))
pUncompressedCDAudio = cp + strlen("a=rtpmap:96 L16/44100/2");
// if (!strncmp(cp, "m=audio", strlen("m=audio")))
// pAudioMediaInfo = cp + strlen("m=audio");
if (!strncmp(cp, "o=iTunes", strlen("o=iTunes")))
pssid = cp + strlen("o=iTunes");
if (!strncmp(cp, "a=fmtp:", strlen("a=fmtp:")))
pfmtp = cp + strlen("a=fmtp:");
if (!strncmp(cp, "a=aesiv:", strlen("a=aesiv:")))
paesiv = cp + strlen("a=aesiv:");
if (!strncmp(cp, "a=rsaaeskey:", strlen("a=rsaaeskey:")))
prsaaeskey = cp + strlen("a=rsaaeskey:");
if (!strncmp(cp, "a=min-latency:", strlen("a=min-latency:")))
pminlatency = cp + strlen("a=min-latency:");
if (!strncmp(cp, "a=max-latency:", strlen("a=max-latency:")))
pmaxlatency = cp + strlen("a=max-latency:");
cp = next;
}
if (pUncompressedCDAudio) {
debug(2, "An uncompressed PCM stream has been detected.");
conn->stream.type = ast_uncompressed;
conn->max_frames_per_packet = 352; // number of audio frames per packet.
conn->input_rate = 44100;
conn->input_num_channels = 2;
conn->input_bit_depth = 16;
conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
/*
int y = strlen(pAudioMediaInfo);
if (y > 0) {
char obf[4096];
if (y > 4096)
y = 4096;
char *p = pAudioMediaInfo;
char *obfp = obf;
int obfc;
for (obfc = 0; obfc < y; obfc++) {
snprintf(obfp, 3, "%02X", (unsigned int)*p);
p++;
obfp += 2;
};
*obfp = 0;
debug(1, "AudioMediaInfo: \"%s\".", obf);
}
*/
}
if (pssid) {
uint32_t ssid = uatoi(pssid);
debug(3, "Synchronisation Source Identifier: %08X,%u", ssid, ssid);
}
if (pminlatency) {
conn->minimum_latency = atoi(pminlatency);
debug(3, "Minimum latency %d specified", conn->minimum_latency);
}
if (pmaxlatency) {
conn->maximum_latency = atoi(pmaxlatency);
debug(3, "Maximum latency %d specified", conn->maximum_latency);
}
if ((paesiv == NULL) && (prsaaeskey == NULL)) {
// debug(1,"Unencrypted session requested?");
conn->stream.encrypted = 0;
} else if ((paesiv != NULL) && (prsaaeskey != NULL)) {
conn->stream.encrypted = 1;
// debug(1,"Encrypted session requested");
} else {
warn("Invalid Announce message -- missing paesiv or prsaaeskey.");
goto out;
}
if (conn->stream.encrypted) {
int len, keylen;
uint8_t *aesiv = base64_dec(paesiv, &len);
if (len != 16) {
warn("client announced aeskey of %d bytes, wanted 16", len);
free(aesiv);
goto out;
}
memcpy(conn->stream.aesiv, aesiv, 16);
free(aesiv);
uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len);
uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY);
free(rsaaeskey);
if (keylen != 16) {
warn("client announced rsaaeskey of %d bytes, wanted 16", keylen);
free(aeskey);
goto out;
}
memcpy(conn->stream.aeskey, aeskey, 16);
free(aeskey);
}
if (pfmtp) {
conn->stream.type = ast_apple_lossless;
debug(3, "An ALAC stream has been detected.");
// Set reasonable connection defaults
conn->stream.fmtp[0] = 96;
conn->stream.fmtp[1] = 352;
conn->stream.fmtp[2] = 0;
conn->stream.fmtp[3] = 16;
conn->stream.fmtp[4] = 40;
conn->stream.fmtp[5] = 10;
conn->stream.fmtp[6] = 14;
conn->stream.fmtp[7] = 2;
conn->stream.fmtp[8] = 255;
conn->stream.fmtp[9] = 0;
conn->stream.fmtp[10] = 0;
conn->stream.fmtp[11] = 44100;
unsigned int i = 0;
unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
char *found;
while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) {
conn->stream.fmtp[i++] = atoi(found);
}
// here we should check the sanity of the fmtp values
// for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
// debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]);
// set the parameters of the player (as distinct from the parameters of the decoder -- that's
// done later).
conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet.
conn->input_rate = conn->stream.fmtp[11];
conn->input_num_channels = conn->stream.fmtp[7];
conn->input_bit_depth = conn->stream.fmtp[3];
conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
}
if (conn->stream.type == ast_unknown) {
warn("Can not process the following ANNOUNCE message:");
// print each line of the request content
// the problem is that nextline has replace all returns, newlines, etc. by
// NULLs
char *cp = req->content;
int cp_left = req->contentlength;
while (cp_left > 1) {
if (strlen(cp) != 0)
warn(" %s", cp);
cp += strlen(cp) + 1;
cp_left -= strlen(cp) + 1;
}
goto out;
}
char *hdr = msg_get_header(req, "X-Apple-Client-Name");
if (hdr) {
debug(1, "Play connection from device named \"%s\" on RTSP conversation thread %d.", hdr,
conn->connection_number);
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'snam', hdr, strlen(hdr), req, 1);
#endif
}
hdr = msg_get_header(req, "User-Agent");
if (hdr) {
conn->UserAgent = strdup(hdr);
debug(2, "Play connection from user agent \"%s\" on RTSP conversation thread %d.", hdr,
conn->connection_number);
// if the user agent is AirPlay and has a version number of 353 or less (from iOS 11.1,2)
// use the older way of calculating the latency
char *p = strstr(hdr, "AirPlay");
if (p) {
p = strchr(p, '/');
if (p) {
conn->AirPlayVersion = atoi(p + 1);
debug(2, "AirPlay version %d detected.", conn->AirPlayVersion);
}
}
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1);
#endif
}
resp->respcode = 200;
} else {
resp->respcode = 453;
debug(1, "Connection %d: ANNOUNCE failed because another connection is already playing.",
conn->connection_number);
}
out:
if (resp->respcode != 200 && resp->respcode != 453) {
debug(1, "Connection %d: Error in handling ANNOUNCE. Unlocking the play lock.",
conn->connection_number);
release_play_lock(conn);
}
}
#ifdef CONFIG_AIRPLAY_2
static struct method_handler {
char *method;
void (*ap1_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 1
void (*ap2_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 2
} method_handlers[] = {{"OPTIONS", handle_options, handle_options},
{"ANNOUNCE", handle_announce, handle_announce},
{"FLUSH", handle_flush, handle_flush},
{"TEARDOWN", handle_teardown, handle_teardown_2},
{"SETUP", handle_setup, handle_setup_2},
{"GET_PARAMETER", handle_get_parameter, handle_get_parameter},
{"SET_PARAMETER", handle_set_parameter, handle_set_parameter},
{"RECORD", handle_record, handle_record_2},
{"GET", handle_get, handle_get},
{"POST", handle_post, handle_post},
{"SETPEERS", handle_unimplemented_ap1, handle_setpeers},
{"SETRATEANCHORTI", handle_unimplemented_ap1, handle_setrateanchori},
{"FLUSHBUFFERED", handle_unimplemented_ap1, handle_flushbuffered},
{"SETRATE", handle_unimplemented_ap1, handle_setrate},
{NULL, NULL, NULL}};
#else
static struct method_handler {
char *method;
void (*handler)(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp); // for AirPlay 1 only
} method_handlers[] = {{"OPTIONS", handle_options},
{"GET", handle_get},
{"POST", handle_post},
{"ANNOUNCE", handle_announce},
{"FLUSH", handle_flush},
{"TEARDOWN", handle_teardown},
{"SETUP", handle_setup},
{"GET_PARAMETER", handle_get_parameter},
{"SET_PARAMETER", handle_set_parameter},
{"RECORD", handle_record},
{NULL, NULL}};
#endif
static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
char *hdr = msg_get_header(req, "Apple-Challenge");
if (!hdr)
return;
SOCKADDR fdsa;
socklen_t sa_len = sizeof(fdsa);
getsockname(fd, (struct sockaddr *)&fdsa, &sa_len);
int chall_len;
uint8_t *chall = base64_dec(hdr, &chall_len);
if (chall == NULL)
die("null chall in apple_challenge");
uint8_t buf[48], *bp = buf;
int i;
memset(buf, 0, sizeof(buf));
if (chall_len > 16) {
warn("oversized Apple-Challenge!");
free(chall);
return;
}
memcpy(bp, chall, chall_len);
free(chall);
bp += chall_len;
#ifdef AF_INET6
if (fdsa.SAFAMILY == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa);
memcpy(bp, sa6->sin6_addr.s6_addr, 16);
bp += 16;
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa);
memcpy(bp, &sa->sin_addr.s_addr, 4);
bp += 4;
}
for (i = 0; i < 6; i++)
*bp++ = config.ap1_prefix[i];
int buflen, resplen;
buflen = bp - buf;
if (buflen < 0x20)
buflen = 0x20;
uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH);
char *encoded = base64_enc(challresp, resplen);
if (encoded == NULL)
die("could not allocate memory for \"encoded\"");
// strip the padding.
char *padding = strchr(encoded, '=');
if (padding)
*padding = 0;
msg_add_header(resp, "Apple-Response", encoded); // will be freed when the response is freed.
free(challresp);
free(encoded);
}
static char *make_nonce(void) {
uint8_t random[8];
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0)
die("could not open /dev/urandom!");
// int ignore =
if (read(fd, random, sizeof(random)) != sizeof(random))
debug(1, "Error reading /dev/urandom");
close(fd);
return base64_enc(random, 8); // returns a pointer to malloc'ed memory
}
static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
if (!config.password)
return 0;
if (!*nonce) {
*nonce = make_nonce();
goto authenticate;
}
char *hdr = msg_get_header(req, "Authorization");
if (!hdr || strncmp(hdr, "Digest ", 7))
goto authenticate;
char *realm = strstr(hdr, "realm=\"");
char *username = strstr(hdr, "username=\"");
char *response = strstr(hdr, "response=\"");
char *uri = strstr(hdr, "uri=\"");
if (!realm || !username || !response || !uri)
goto authenticate;
char *quote;
realm = strchr(realm, '"') + 1;
if (!(quote = strchr(realm, '"')))
goto authenticate;
*quote = 0;
username = strchr(username, '"') + 1;
if (!(quote = strchr(username, '"')))
goto authenticate;
*quote = 0;
response = strchr(response, '"') + 1;
if (!(quote = strchr(response, '"')))
goto authenticate;
*quote = 0;
uri = strchr(uri, '"') + 1;
if (!(quote = strchr(uri, '"')))
goto authenticate;
*quote = 0;
uint8_t digest_urp[16], digest_mu[16], digest_total[16];
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
MD5_Init(&ctx);
MD5_Update(&ctx, username, strlen(username));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, realm, strlen(realm));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, config.password, strlen(config.password));
MD5_Final(digest_urp, &ctx);
MD5_Init(&ctx);
MD5_Update(&ctx, req->method, strlen(req->method));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, uri, strlen(uri));
MD5_Final(digest_mu, &ctx);
pthread_setcancelstate(oldState, NULL);
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_context tctx;
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password));
mbedtls_md5_finish_ret(&tctx, digest_urp);
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri));
mbedtls_md5_finish_ret(&tctx, digest_mu);
#else
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
mbedtls_md5_finish(&tctx, digest_urp);
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
mbedtls_md5_finish(&tctx, digest_mu);
#endif
#endif
#ifdef CONFIG_POLARSSL
md5_context tctx;
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)username, strlen(username));
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
md5_finish(&tctx, digest_urp);
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
md5_finish(&tctx, digest_mu);
#endif
int i;
unsigned char buf[33];
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]);
#ifdef CONFIG_OPENSSL
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
MD5_Init(&ctx);
MD5_Update(&ctx, buf, 32);
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, *nonce, strlen(*nonce));
MD5_Update(&ctx, ":", 1);
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
MD5_Update(&ctx, buf, 32);
MD5_Final(digest_total, &ctx);
pthread_setcancelstate(oldState, NULL);
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, buf, 32);
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
mbedtls_md5_update_ret(&tctx, buf, 32);
mbedtls_md5_finish_ret(&tctx, digest_total);
#else
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_finish(&tctx, digest_total);
#endif
#endif
#ifdef CONFIG_POLARSSL
md5_starts(&tctx);
md5_update(&tctx, buf, 32);
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
md5_update(&tctx, (unsigned char *)":", 1);
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
md5_update(&tctx, buf, 32);
md5_finish(&tctx, digest_total);
#endif
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_total[i]);
if (!strcmp(response, (const char *)buf))
return 0;
warn("Password authorization failed.");
authenticate:
resp->respcode = 401;
int hdrlen = strlen(*nonce) + 40;
char *authhdr = malloc(hdrlen);
snprintf(authhdr, hdrlen, "Digest realm=\"raop\", nonce=\"%s\"", *nonce);
msg_add_header(resp, "WWW-Authenticate", authhdr);
free(authhdr);
return 1;
}
void rtsp_conversation_thread_cleanup_function(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(2, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
conn->connection_number);
#ifdef CONFIG_AIRPLAY_2
// AP2
teardown_phase_one(conn);
teardown_phase_two(conn);
#else
// AP1
if (have_play_lock(conn)) {
teardown(conn);
release_play_lock(conn);
}
#endif
debug(3, "Connection %d: terminating -- closing timing, control and audio sockets...",
conn->connection_number);
if (conn->control_socket) {
debug(3, "Connection %d: terminating -- closing control_socket %d.", conn->connection_number,
conn->control_socket);
close(conn->control_socket);
conn->control_socket = 0;
}
if (conn->timing_socket) {
debug(3, "Connection %d: terminating -- closing timing_socket %d.", conn->connection_number,
conn->timing_socket);
close(conn->timing_socket);
conn->timing_socket = 0;
}
if (conn->audio_socket) {
debug(3, "Connection %d: terminating -- closing audio_socket %d.", conn->connection_number,
conn->audio_socket);
close(conn->audio_socket);
conn->audio_socket = 0;
}
if (conn->fd > 0) {
debug(2,
"Connection %d: terminating -- closing RTSP connection socket %d: from %s:%u to self at "
"%s:%u.",
conn->connection_number, conn->fd, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
close(conn->fd);
conn->fd = 0;
}
if (conn->auth_nonce) {
free(conn->auth_nonce);
conn->auth_nonce = NULL;
}
#ifdef CONFIG_AIRPLAY_2
buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.plaintext_read_buffer, -1);
buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.encrypted_read_buffer, -1);
pair_cipher_free(conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx);
pair_setup_free(conn->ap2_pairing_context.setup_ctx);
pair_verify_free(conn->ap2_pairing_context.verify_ctx);
if (conn->airplay_gid) {
free(conn->airplay_gid);
conn->airplay_gid = NULL;
}
#endif
rtp_terminate(conn);
if (conn->dacp_id) {
free(conn->dacp_id);
conn->dacp_id = NULL;
}
if (conn->UserAgent) {
free(conn->UserAgent);
conn->UserAgent = NULL;
}
// remove flow control and mutexes
int rc = pthread_mutex_destroy(&conn->volume_control_mutex);
if (rc)
debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number,
rc);
rc = pthread_cond_destroy(&conn->flowcontrol);
if (rc)
debug(1, "Connection %d: error %d destroying flow control condition variable.",
conn->connection_number, rc);
rc = pthread_mutex_destroy(&conn->ab_mutex);
if (rc)
debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc);
rc = pthread_mutex_destroy(&conn->flush_mutex);
if (rc)
debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc);
debug(3, "Cancel watchdog thread.");
pthread_cancel(conn->player_watchdog_thread);
debug(3, "Join watchdog thread.");
pthread_join(conn->player_watchdog_thread, NULL);
debug(3, "Delete watchdog mutex.");
pthread_mutex_destroy(&conn->watchdog_mutex);
// debug(3, "Connection %d: Checking play lock.", conn->connection_number);
// release_play_lock(conn);
debug(2, "Connection %d: Closed.", conn->connection_number);
conn->running = 0;
pthread_setcancelstate(oldState, NULL);
}
void msg_cleanup_function(void *arg) {
// debug(3, "msg_cleanup_function called.");
msg_free((rtsp_message **)arg);
}
static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
// create the watchdog mutex, initialise the watchdog time and start the watchdog thread;
conn->watchdog_bark_time = get_absolute_time_in_ns();
pthread_mutex_init(&conn->watchdog_mutex, NULL);
pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn);
int rc = pthread_mutex_init(&conn->flush_mutex, NULL);
if (rc)
die("Connection %d: error %d initialising flush_mutex.", conn->connection_number, rc);
rc = pthread_mutex_init(&conn->ab_mutex, NULL);
if (rc)
die("Connection %d: error %d initialising ab_mutex.", conn->connection_number, rc);
rc = pthread_cond_init(&conn->flowcontrol, NULL);
if (rc)
die("Connection %d: error %d initialising flow control condition variable.",
conn->connection_number, rc);
rc = pthread_mutex_init(&conn->volume_control_mutex, NULL);
if (rc)
die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc);
// nothing before this is cancellable
pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn);
rtp_initialise(conn);
char *hdr = NULL;
enum rtsp_read_request_response reply;
int rtsp_read_request_attempt_count = 1; // 1 means exit immediately
rtsp_message *req, *resp;
#ifdef CONFIG_AIRPLAY_2
conn->ap2_audio_buffer_size = 1024 * 1024 * 8;
#endif
while (conn->stop == 0) {
int debug_level = 3; // for printing the request and response
reply = rtsp_read_request(conn, &req);
if (reply == rtsp_read_request_response_ok) {
pthread_cleanup_push(msg_cleanup_function, (void *)&req);
resp = msg_init();
pthread_cleanup_push(msg_cleanup_function, (void *)&resp);
resp->respcode = 501; // Not Implemented
int dl = debug_level;
if ((strcmp(req->method, "OPTIONS") == 0) ||
(strcmp(req->method, "POST") ==
0)) // the options message is very common, so don't log it until level 3
dl = 3;
if (conn->airplay_stream_category == remote_control_stream) {
debug(dl, "Connection %d (RC): Received an RTSP Packet of type \"%s\":",
conn->connection_number, req->method),
debug_log_rtsp_message(dl, NULL, req);
} else {
debug(dl, "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
req->method),
debug_log_rtsp_message(dl, NULL, req);
}
apple_challenge(conn->fd, req, resp);
hdr = msg_get_header(req, "CSeq");
if (hdr)
msg_add_header(resp, "CSeq", hdr);
// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
#ifdef CONFIG_AIRPLAY_2
msg_add_header(resp, "Server", "AirTunes/366.0");
#else
msg_add_header(resp, "Server", "AirTunes/105.1");
#endif
if ((conn->authorized == 1) || (rtsp_auth(&conn->auth_nonce, req, resp)) == 0) {
conn->authorized = 1; // it must have been authorized or didn't need a password
struct method_handler *mh;
int method_selected = 0;
for (mh = method_handlers; mh->method; mh++) {
if (!strcmp(mh->method, req->method)) {
method_selected = 1;
#ifdef CONFIG_AIRPLAY_2
if (conn->airplay_type == ap_1)
mh->ap1_handler(conn, req, resp);
else
mh->ap2_handler(conn, req, resp);
#else
mh->handler(conn, req, resp);
#endif
break;
}
}
if (method_selected == 0) {
debug(1,
"Connection %d: Unrecognised and unhandled rtsp request \"%s\". HTTP Response Code "
"501 (\"Not Implemented\") returned.",
conn->connection_number, req->method);
int y = req->contentlength;
if (y > 0) {
char obf[4096];
if (y > 4096)
y = 4096;
char *p = req->content;
char *obfp = obf;
int obfc;
for (obfc = 0; obfc < y; obfc++) {
snprintf(obfp, 3, "%02X", (unsigned int)*p);
p++;
obfp += 2;
};
*obfp = 0;
debug(1, "Content: \"%s\".", obf);
}
}
}
if (conn->airplay_stream_category == remote_control_stream) {
debug(dl, "Connection %d (RC): RTSP Response:", conn->connection_number);
debug_log_rtsp_message(dl, NULL, resp);
} else {
debug(dl, "Connection %d: RTSP Response:", conn->connection_number);
debug_log_rtsp_message(dl, NULL, resp);
}
if (conn->stop == 0) {
int err = msg_write_response(conn, resp);
if (err) {
debug(1,
"Connection %d: Unable to write an RTSP message response. Terminating the "
"connection.",
conn->connection_number);
struct linger so_linger;
so_linger.l_onoff = 1; // "true"
so_linger.l_linger = 0;
err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
if (err)
debug(1, "Could not set the RTSP socket to abort due to a write error on closing.");
conn->stop = 1;
// if (debuglev >= 1)
// debuglev = 3; // see what happens next
}
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
} else {
int tstop = 0;
if (reply == rtsp_read_request_response_immediate_shutdown_requested)
tstop = 1;
else if ((reply == rtsp_read_request_response_channel_closed) ||
(reply == rtsp_read_request_response_read_error)) {
if (conn->player_thread) {
rtsp_read_request_attempt_count--;
if (rtsp_read_request_attempt_count == 0) {
tstop = 1;
if (reply == rtsp_read_request_response_read_error) {
struct linger so_linger;
so_linger.l_onoff = 1; // "true"
so_linger.l_linger = 0;
int err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
if (err)
debug(1, "Could not set the RTSP socket to abort due to a read error on closing.");
}
// debuglev = 3; // see what happens next
} else {
if (reply == rtsp_read_request_response_channel_closed)
debug(2,
"Connection %d: RTSP channel unexpectedly closed -- will try again %d time(s).",
conn->connection_number, rtsp_read_request_attempt_count);
if (reply == rtsp_read_request_response_read_error)
debug(2, "Connection %d: RTSP channel read error -- will try again %d time(s).",
conn->connection_number, rtsp_read_request_attempt_count);
usleep(20000);
}
} else {
tstop = 1;
}
} else if (reply == rtsp_read_request_response_bad_packet) {
char *response_text = "RTSP/1.0 400 Bad Request\r\nServer: AirTunes/105.1\r\n\r\n";
ssize_t reply = write(conn->fd, response_text, strlen(response_text));
if (reply == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno,
(char *)errorstring);
} else if (reply != (ssize_t)strlen(response_text)) {
debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.",
strlen(response_text), reply);
}
} else {
debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.",
conn->connection_number, (int)reply);
}
if (tstop) {
debug(3, "Connection %d: Terminate RTSP connection.", conn->connection_number);
conn->stop = 1;
}
}
}
pthread_cleanup_pop(1);
pthread_exit(NULL);
debug(1, "Connection %d: RTSP thread exit.", conn->connection_number);
}
/*
// this function is not thread safe.
static const char *format_address(struct sockaddr *fsa) {
static char string[INETx_ADDRSTRLEN];
void *addr;
#ifdef AF_INET6
if (fsa->sa_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa);
addr = &(sa6->sin6_addr);
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in *)(fsa);
addr = &(sa->sin_addr);
}
return inet_ntop(fsa->sa_family, addr, string, sizeof(string));
}
*/
void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(2, "rtsp_listen_loop_cleanup_handler called.");
cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners
int *sockfd = (int *)arg;
if (sockfd) {
int i;
for (i = 1; i <= sockfd[0]; i++) {
debug(2, "closing socket %d.", sockfd[i]);
close(sockfd[i]);
}
free(sockfd);
}
pthread_setcancelstate(oldState, NULL);
}
void *rtsp_listen_loop(__attribute((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct addrinfo hints, *info, *p;
char portstr[6];
int *sockfd = NULL;
int nsock = 0;
int i, ret;
playing_conn = NULL; // the data structure representing the connection that has the player.
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
snprintf(portstr, 6, "%d", config.port);
// debug(1,"listen socket port request is \"%s\".",portstr);
ret = getaddrinfo(NULL, portstr, &hints, &info);
if (ret) {
die("getaddrinfo failed: %s", gai_strerror(ret));
}
for (p = info; p; p = p->ai_next) {
ret = 0;
int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP);
int yes = 1;
// Handle socket open failures if protocol unavailable (or IPV6 not handled)
if (fd != -1) {
// Set the RTSP socket to close on exec() of child processes
// otherwise background run_this_before_play_begins or run_this_after_play_ends commands
// that are sleeping prevent the daemon from being restarted because
// the listening RTSP port is still in use.
// See: https://github.com/mikebrady/shairport-sync/issues/329
fcntl(fd, F_SETFD, FD_CLOEXEC);
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
struct timeval tv;
tv.tv_sec = 3; // three seconds write timeout
tv.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "Error %d setting send timeout for rtsp writeback.", errno);
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
tv.tv_sec = config.timeout; // 120 seconds read timeout by default.
tv.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "Error %d setting read timeout for rtsp connection.", errno);
}
#ifdef IPV6_V6ONLY
// some systems don't support v4 access on v6 sockets, but some do.
// since we need to account for two sockets we might as well
// always.
if (p->ai_family == AF_INET6) {
ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
}
#endif
if (!ret)
ret = bind(fd, p->ai_addr, p->ai_addrlen);
// one of the address families will fail on some systems that
// report its availability. do not complain.
if (ret) {
char *family;
#ifdef AF_INET6
if (p->ai_family == AF_INET6) {
family = "IPv6";
} else
#endif
family = "IPv4";
debug(1, "unable to listen on %s port %d. The error is: \"%s\".", family, config.port,
strerror(errno));
} else {
listen(fd, 255);
nsock++;
sockfd = realloc(sockfd, (nsock + 1) * sizeof(int));
sockfd[nsock] = fd;
sockfd[0] = nsock; // the first entry is the number of sockets in the array
}
}
/*
listen(fd, 5);
nsock++;
sockfd = realloc(sockfd, nsock * sizeof(int));
sockfd[nsock - 1] = fd;
*/
}
freeaddrinfo(info);
if (nsock) {
int maxfd = -1;
fd_set fds;
FD_ZERO(&fds);
// skip the first element in sockfd -- it's the count
for (i = 1; i <= nsock; i++) {
if (sockfd[i] > maxfd)
maxfd = sockfd[i];
}
char **t1 = txt_records; // ap1 test records
char **t2 = NULL; // possibly two text records
#ifdef CONFIG_AIRPLAY_2
// make up a secondary set of text records
t2 = secondary_txt_records; // second set of text records in AirPlay 2 only
#endif
build_bonjour_strings(NULL); // no conn yet
mdns_register(t1, t2); // note that the dacp thread could still be using the mdns stuff after
// all player threads have been terminated, so mdns_unregister can't be
// in the rtsp_listen_loop cleanup.
pthread_setcancelstate(oldState, NULL);
int acceptfd;
struct timeval tv;
pthread_cleanup_push(rtsp_listen_loop_cleanup_handler, (void *)sockfd);
do {
pthread_testcancel();
tv.tv_sec = 60;
tv.tv_usec = 0;
// skip the first element in sockfd -- it's the count
for (i = 1; i <= nsock; i++)
FD_SET(sockfd[i], &fds);
ret = select(maxfd + 1, &fds, 0, 0, &tv);
if (ret < 0) {
if (errno == EINTR)
continue;
break;
}
cleanup_threads();
acceptfd = -1;
// skip the first element in sockfd -- it's the count
for (i = 1; i <= nsock; i++) {
if (FD_ISSET(sockfd[i], &fds)) {
acceptfd = sockfd[i];
break;
}
}
if (acceptfd < 0) // timeout
continue;
rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info));
if (conn == 0)
die("Couldn't allocate memory for an rtsp_conn_info record.");
memset(conn, 0, sizeof(rtsp_conn_info));
conn->connection_number = RTSP_connection_index++;
#ifdef CONFIG_AIRPLAY_2
conn->airplay_type = ap_2; // changed if an ANNOUNCE is received
conn->timing_type = ts_ptp; // changed if an ANNOUNCE is received
#endif
socklen_t size_of_reply = sizeof(SOCKADDR);
conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &size_of_reply);
if (conn->fd < 0) {
debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number,
config.port);
perror("failed to accept connection");
free(conn);
} else {
size_of_reply = sizeof(SOCKADDR);
if (getsockname(conn->fd, (struct sockaddr *)&conn->local, &size_of_reply) == 0) {
// Thanks to https://holmeshe.me/network-essentials-setsockopt-SO_KEEPALIVE/ for this.
// turn on keepalive stuff -- wait for keepidle + (keepcnt * keepinttvl time) seconds
// before giving up an ETIMEOUT error is returned if the keepalive check fails
int keepAliveIdleTime = 35; // wait this many seconds before checking for a dropped client
int keepAliveCount = 5; // check this many times
int keepAliveInterval = 5; // wait this many seconds between checks
#if defined COMPILE_FOR_BSD || defined COMPILE_FOR_OSX
#define SOL_OPTION IPPROTO_TCP
#else
#define SOL_OPTION SOL_TCP
#endif
#ifdef COMPILE_FOR_OSX
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPALIVE
#else
#define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPIDLE
#endif
if (setsockopt(conn->fd, SOL_OPTION, KEEP_ALIVE_OR_IDLE_OPTION,
(void *)&keepAliveIdleTime, sizeof(keepAliveIdleTime))) {
debug(1, "can't set the keepidle wait time");
}
if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPCNT, (void *)&keepAliveCount,
sizeof(keepAliveCount))) {
debug(1, "can't set the keepidle missing count");
}
if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPINTVL, (void *)&keepAliveInterval,
sizeof(keepAliveInterval))) {
debug(1, "can't set the keepidle missing count interval");
};
// initialise the connection info
void *client_addr = NULL, *self_addr = NULL;
conn->connection_ip_family = conn->local.SAFAMILY;
#ifdef AF_INET6
if (conn->connection_ip_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->remote;
client_addr = &(sa6->sin6_addr);
conn->client_rtsp_port = ntohs(sa6->sin6_port);
sa6 = (struct sockaddr_in6 *)&conn->local;
self_addr = &(sa6->sin6_addr);
conn->self_rtsp_port = ntohs(sa6->sin6_port);
conn->self_scope_id = sa6->sin6_scope_id;
}
#endif
if (conn->connection_ip_family == AF_INET) {
struct sockaddr_in *sa4 = (struct sockaddr_in *)&conn->remote;
client_addr = &(sa4->sin_addr);
conn->client_rtsp_port = ntohs(sa4->sin_port);
sa4 = (struct sockaddr_in *)&conn->local;
self_addr = &(sa4->sin_addr);
conn->self_rtsp_port = ntohs(sa4->sin_port);
}
inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string,
sizeof(conn->client_ip_string));
inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
sizeof(conn->self_ip_string));
debug(2, "Connection %d: New connection from %s:%u to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
conn->connection_start_time = get_absolute_time_in_ns();
} else {
debug(1, "Error figuring out Shairport Sync's own IP number.");
}
ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func,
conn); // also acts as a memory barrier
if (ret) {
char errorstring[1024];
strerror_r(ret, (char *)errorstring, sizeof(errorstring));
die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".",
conn->connection_number, ret, (char *)errorstring);
}
debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number);
conn->running = 1; // this must happen before the thread is tracked
track_thread(conn);
}
} while (1);
pthread_cleanup_pop(1); // should never happen
} else {
warn("could not establish a service on port %d -- program terminating. Is another instance of "
"Shairport Sync running on this device?",
config.port);
}
debug(1, "Oops -- fell out of the RTSP select loop");
pthread_exit(NULL);
}