Files
AirplayServer/lib/raop_handlers.h
2020-04-19 14:49:21 +02:00

575 lines
25 KiB
C
Executable File

/**
* Copyright (C) 2018 Juho Vähä-Herttua
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
/* This file should be only included from raop.c as it defines static handler
* functions and depends on raop internals */
#include "plist/plist/plist.h"
#include "dnssdint.h"
#include "utils.h"
#include <ctype.h>
#include <stdlib.h>
typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *,
http_response_t *, char **, int *);
static void
raop_handler_info(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
assert(conn->raop->dnssd);
int airplay_txt_len = 0;
const char *airplay_txt = dnssd_get_airplay_txt(conn->raop->dnssd, &airplay_txt_len);
int name_len = 0;
const char *name = dnssd_get_name(conn->raop->dnssd, &name_len);
int hw_addr_raw_len = 0;
const char *hw_addr_raw = dnssd_get_hw_addr(conn->raop->dnssd, &hw_addr_raw_len);
char *hw_addr = calloc(1, 3 * hw_addr_raw_len);
int hw_addr_len = utils_hwaddr_airplay(hw_addr, 3 * hw_addr_raw_len, hw_addr_raw, hw_addr_raw_len);
int pk_len = 0;
char *pk = utils_parse_hex(AIRPLAY_PK, strlen(AIRPLAY_PK), &pk_len);
plist_t r_node = plist_new_dict();
plist_t txt_airplay_node = plist_new_data(airplay_txt, airplay_txt_len);
plist_dict_set_item(r_node, "txtAirPlay", txt_airplay_node);
plist_t features_node = plist_new_uint((uint64_t) 0x1E << 32 | 0x5A7FFFF7);
plist_dict_set_item(r_node, "features", features_node);
plist_t name_node = plist_new_string(name);
plist_dict_set_item(r_node, "name", name_node);
plist_t audio_formats_node = plist_new_array();
plist_t audio_format_0_node = plist_new_dict();
plist_t audio_format_0_type_node = plist_new_uint(100);
plist_t audio_format_0_audio_input_formats_node = plist_new_uint(67108860);
plist_t audio_format_0_audio_output_formats_node = plist_new_uint(67108860);
plist_dict_set_item(audio_format_0_node, "type", audio_format_0_type_node);
plist_dict_set_item(audio_format_0_node, "audioInputFormats", audio_format_0_audio_input_formats_node);
plist_dict_set_item(audio_format_0_node, "audioOutputFormats", audio_format_0_audio_output_formats_node);
plist_array_append_item(audio_formats_node, audio_format_0_node);
plist_t audio_format_1_node = plist_new_dict();
plist_t audio_format_1_type_node = plist_new_uint(101);
plist_t audio_format_1_audio_input_formats_node = plist_new_uint(67108860);
plist_t audio_format_1_audio_output_formats_node = plist_new_uint(67108860);
plist_dict_set_item(audio_format_1_node, "type", audio_format_1_type_node);
plist_dict_set_item(audio_format_1_node, "audioInputFormats", audio_format_1_audio_input_formats_node);
plist_dict_set_item(audio_format_1_node, "audioOutputFormats", audio_format_1_audio_output_formats_node);
plist_array_append_item(audio_formats_node, audio_format_1_node);
plist_dict_set_item(r_node, "audioFormats", audio_formats_node);
plist_t pi_node = plist_new_string(AIRPLAY_PI);
plist_dict_set_item(r_node, "pi", pi_node);
plist_t vv_node = plist_new_uint(strtol(AIRPLAY_VV, NULL, 10));
plist_dict_set_item(r_node, "vv", vv_node);
plist_t status_flags_node = plist_new_uint(68);
plist_dict_set_item(r_node, "statusFlags", status_flags_node);
plist_t keep_alive_low_power_node = plist_new_bool(1);
plist_dict_set_item(r_node, "keepAliveLowPower", keep_alive_low_power_node);
plist_t source_version_node = plist_new_string(AIRPLAY_SRCVERS);
plist_dict_set_item(r_node, "sourceVersion", source_version_node);
plist_t pk_node = plist_new_data(pk, pk_len);
plist_dict_set_item(r_node, "pk", pk_node);
plist_t keep_alive_send_stats_as_body_node = plist_new_bool(1);
plist_dict_set_item(r_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node);
plist_t device_id_node = plist_new_string(hw_addr);
plist_dict_set_item(r_node, "deviceID", device_id_node);
plist_t audio_latencies_node = plist_new_array();
plist_t audio_latencies_0_node = plist_new_dict();
plist_t audio_latencies_0_output_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_0_type_node = plist_new_uint(100);
plist_t audio_latencies_0_audio_type_node = plist_new_string("default");
plist_t audio_latencies_0_input_latency_micros_node = plist_new_uint(0);
plist_dict_set_item(audio_latencies_0_node, "outputLatencyMicros", audio_latencies_0_output_latency_micros_node);
plist_dict_set_item(audio_latencies_0_node, "type", audio_latencies_0_type_node);
plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node);
plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node);
plist_array_append_item(audio_latencies_node, audio_latencies_0_node);
plist_t audio_latencies_1_node = plist_new_dict();
plist_t audio_latencies_1_output_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_1_type_node = plist_new_uint(101);
plist_t audio_latencies_1_audio_type_node = plist_new_string("default");
plist_t audio_latencies_1_input_latency_micros_node = plist_new_uint(0);
plist_dict_set_item(audio_latencies_1_node, "outputLatencyMicros", audio_latencies_1_output_latency_micros_node);
plist_dict_set_item(audio_latencies_1_node, "type", audio_latencies_1_type_node);
plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node);
plist_dict_set_item(audio_latencies_1_node, "inputLatencyMicros", audio_latencies_1_input_latency_micros_node);
plist_array_append_item(audio_latencies_node, audio_latencies_1_node);
plist_dict_set_item(r_node, "audioLatencies", audio_latencies_node);
plist_t model_node = plist_new_string(GLOBAL_MODEL);
plist_dict_set_item(r_node, "model", model_node);
plist_t mac_address_node = plist_new_string(hw_addr);
plist_dict_set_item(r_node, "macAddress", mac_address_node);
plist_t displays_node = plist_new_array();
plist_t displays_0_node = plist_new_dict();
plist_t displays_0_uuid_node = plist_new_string("e0ff8a27-6738-3d56-8a16-cc53aacee925");
plist_t displays_0_width_physical_node = plist_new_uint(0);
plist_t displays_0_height_physical_node = plist_new_uint(0);
plist_t displays_0_width_node = plist_new_uint(1920);
plist_t displays_0_height_node = plist_new_uint(1080);
plist_t displays_0_width_pixels_node = plist_new_uint(1920);
plist_t displays_0_height_pixels_node = plist_new_uint(1080);
plist_t displays_0_rotation_node = plist_new_bool(0);
plist_t displays_0_refresh_rate_node = plist_new_real(1.0 / 60.0);
plist_t displays_0_overscanned_node = plist_new_bool(1);
plist_t displays_0_features = plist_new_uint(14);
plist_dict_set_item(displays_0_node, "uuid", displays_0_uuid_node);
plist_dict_set_item(displays_0_node, "widthPhysical", displays_0_width_physical_node);
plist_dict_set_item(displays_0_node, "heightPhysical", displays_0_height_physical_node);
plist_dict_set_item(displays_0_node, "width", displays_0_width_node);
plist_dict_set_item(displays_0_node, "height", displays_0_height_node);
plist_dict_set_item(displays_0_node, "widthPixels", displays_0_width_pixels_node);
plist_dict_set_item(displays_0_node, "heightPixels", displays_0_height_pixels_node);
plist_dict_set_item(displays_0_node, "rotation", displays_0_rotation_node);
plist_dict_set_item(displays_0_node, "refreshRate", displays_0_refresh_rate_node);
plist_dict_set_item(displays_0_node, "overscanned", displays_0_overscanned_node);
plist_dict_set_item(displays_0_node, "features", displays_0_features);
plist_array_append_item(displays_node, displays_0_node);
plist_dict_set_item(r_node, "displays", displays_node);
plist_to_bin(r_node, response_data, (uint32_t *) response_datalen);
logger_log(conn->raop->logger, LOGGER_DEBUG, "INFO len = %d", response_datalen);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
free(pk);
free(hw_addr);
}
static void
raop_handler_pairsetup(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
unsigned char public_key[32];
const char *data;
int datalen;
data = http_request_get_data(request, &datalen);
if (datalen != 32) {
logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-setup data");
return;
}
pairing_get_public_key(conn->raop->pairing, public_key);
pairing_session_set_setup_status(conn->pairing);
*response_data = malloc(sizeof(public_key));
if (*response_data) {
http_response_add_header(response, "Content-Type", "application/octet-stream");
memcpy(*response_data, public_key, sizeof(public_key));
*response_datalen = sizeof(public_key);
}
}
static void
raop_handler_pairverify(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
if (pairing_session_check_handshake_status(conn->pairing)) {
return;
}
unsigned char public_key[32];
unsigned char signature[64];
const unsigned char *data;
int datalen;
data = (unsigned char *) http_request_get_data(request, &datalen);
if (datalen < 4) {
logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-verify data");
return;
}
switch (data[0]) {
case 1:
if (datalen != 4 + 32 + 32) {
logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-verify data");
return;
}
/* We can fall through these errors, the result will just be garbage... */
if (pairing_session_handshake(conn->pairing, data + 4, data + 4 + 32)) {
logger_log(conn->raop->logger, LOGGER_ERR, "Error initializing pair-verify handshake");
}
if (pairing_session_get_public_key(conn->pairing, public_key)) {
logger_log(conn->raop->logger, LOGGER_ERR, "Error getting ECDH public key");
}
if (pairing_session_get_signature(conn->pairing, signature)) {
logger_log(conn->raop->logger, LOGGER_ERR, "Error getting ED25519 signature");
}
*response_data = malloc(sizeof(public_key) + sizeof(signature));
if (*response_data) {
http_response_add_header(response, "Content-Type", "application/octet-stream");
memcpy(*response_data, public_key, sizeof(public_key));
memcpy(*response_data + sizeof(public_key), signature, sizeof(signature));
*response_datalen = sizeof(public_key) + sizeof(signature);
}
break;
case 0:
if (datalen != 4 + 64) {
logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-verify data");
return;
}
if (pairing_session_finish(conn->pairing, data + 4)) {
logger_log(conn->raop->logger, LOGGER_ERR, "Incorrect pair-verify signature");
http_response_set_disconnect(response, 1);
return;
}
http_response_add_header(response, "Content-Type", "application/octet-stream");
break;
}
}
static void
raop_handler_fpsetup(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
const unsigned char *data;
int datalen;
data = (unsigned char *) http_request_get_data(request, &datalen);
if (datalen == 16) {
*response_data = malloc(142);
if (*response_data) {
http_response_add_header(response, "Content-Type", "application/octet-stream");
if (!fairplay_setup(conn->fairplay, data, (unsigned char *) *response_data)) {
*response_datalen = 142;
} else {
// Handle error?
free(*response_data);
*response_data = NULL;
}
}
} else if (datalen == 164) {
*response_data = malloc(32);
if (*response_data) {
http_response_add_header(response, "Content-Type", "application/octet-stream");
if (!fairplay_handshake(conn->fairplay, data, (unsigned char *) *response_data)) {
*response_datalen = 32;
} else {
// Handle error?
free(*response_data);
*response_data = NULL;
}
}
} else {
logger_log(conn->raop->logger, LOGGER_ERR, "Invalid fp-setup data length");
return;
}
}
static void
raop_handler_options(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
http_response_add_header(response, "Public", "SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
static void
raop_handler_setup(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
unsigned short remote_cport = 0;
const char *transport;
int use_udp;
const char *dacp_id;
const char *active_remote_header;
const char *data;
int data_len;
data = http_request_get_data(request, &data_len);
dacp_id = http_request_get_header(request, "DACP-ID");
active_remote_header = http_request_get_header(request, "Active-Remote");
if (dacp_id && active_remote_header) {
logger_log(conn->raop->logger, LOGGER_DEBUG, "DACP-ID: %s", dacp_id);
logger_log(conn->raop->logger, LOGGER_DEBUG, "Active-Remote: %s", active_remote_header);
if (conn->raop_rtp) {
raop_rtp_remote_control_id(conn->raop_rtp, dacp_id, active_remote_header);
}
}
transport = http_request_get_header(request, "Transport");
if (transport) {
logger_log(conn->raop->logger, LOGGER_DEBUG, "Transport: %s", transport);
use_udp = strncmp(transport, "RTP/AVP/TCP", 11);
} else {
logger_log(conn->raop->logger, LOGGER_DEBUG, "Transport: null");
use_udp = 0;
}
// Parsing bplist
plist_t req_root_node = NULL;
plist_from_bin(data, data_len, &req_root_node);
plist_t req_streams_node = plist_dict_get_item(req_root_node, "streams");
plist_t req_eiv_node = plist_dict_get_item(req_root_node, "eiv");
plist_t req_ekey_node = plist_dict_get_item(req_root_node, "ekey");
// For the response
plist_t res_root_node = plist_new_dict();
if (PLIST_IS_DATA(req_eiv_node) && PLIST_IS_DATA(req_ekey_node)) {
// The first SETUP call that initializes keys and timing
unsigned char aesiv[16];
unsigned char aeskey[16];
logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1");
// First setup
char* eiv = NULL;
uint64_t eiv_len = 0;
plist_get_data_val(req_eiv_node, &eiv, &eiv_len);
memcpy(aesiv, eiv, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eiv_len = %llu", eiv_len);
char* ekey = NULL;
uint64_t ekey_len = 0;
plist_get_data_val(req_ekey_node, &ekey, &ekey_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey_len = %llu", ekey_len);
// ekey is 72 bytes, aeskey is 16 bytes
int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey);
logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret);
unsigned char ecdh_secret[32];
pairing_get_ecdh_secret_key(conn->pairing, ecdh_secret);
// Time port
uint64_t timing_rport;
plist_t time_note = plist_dict_get_item(req_root_node, "timingPort");
plist_get_uint_val(time_note, &timing_rport);
logger_log(conn->raop->logger, LOGGER_DEBUG, "timing_rport = %llu", timing_rport);
unsigned short timing_lport;
conn->raop_ntp = raop_ntp_init(conn->raop->logger, conn->remote, conn->remotelen, timing_rport);
raop_ntp_start(conn->raop_ntp, &timing_lport);
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv, ecdh_secret);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, ecdh_secret);
plist_t res_event_port_node = plist_new_uint(conn->raop->port);
plist_t res_timing_port_node = plist_new_uint(timing_lport);
plist_dict_set_item(res_root_node, "timingPort", res_timing_port_node);
plist_dict_set_item(res_root_node, "eventPort", res_event_port_node);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", conn->raop->port, timing_lport);
}
// Process stream setup requests
if (PLIST_IS_ARRAY(req_streams_node)) {
plist_t res_streams_node = plist_new_array();
int count = plist_array_get_size(req_streams_node);
for (int i = 0; i < count; i++) {
plist_t req_stream_node = plist_array_get_item(req_streams_node, i);
plist_t req_stream_type_node = plist_dict_get_item(req_stream_node, "type");
uint64_t type;
plist_get_uint_val(req_stream_type_node, &type);
logger_log(conn->raop->logger, LOGGER_DEBUG, "type = %llu", type);
switch (type) {
case 110: {
// Mirroring
unsigned short dport = 0;
plist_t stream_id_node = plist_dict_get_item(req_stream_node, "streamConnectionID");
uint64_t stream_connection_id;
plist_get_uint_val(stream_id_node, &stream_connection_id);
logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID = %llu", stream_connection_id);
if (conn->raop_rtp_mirror) {
raop_rtp_init_mirror_aes(conn->raop_rtp_mirror, stream_connection_id);
raop_rtp_start_mirror(conn->raop_rtp_mirror, use_udp, &dport);
logger_log(conn->raop->logger, LOGGER_DEBUG, "Mirroring initialized successfully");
} else {
logger_log(conn->raop->logger, LOGGER_ERR, "Mirroring not initialized at SETUP, playing will fail!");
http_response_set_disconnect(response, 1);
}
plist_t res_stream_node = plist_new_dict();
plist_t res_stream_data_port_node = plist_new_uint(dport);
plist_t res_stream_type_node = plist_new_uint(110);
plist_dict_set_item(res_stream_node, "dataPort", res_stream_data_port_node);
plist_dict_set_item(res_stream_node, "type", res_stream_type_node);
plist_array_append_item(res_streams_node, res_stream_node);
break;
} case 96: {
// Audio
unsigned short cport = 0, dport = 0;
if (conn->raop_rtp) {
raop_rtp_start_audio(conn->raop_rtp, use_udp, remote_cport, &cport, &dport);
logger_log(conn->raop->logger, LOGGER_DEBUG, "RAOP initialized success");
} else {
logger_log(conn->raop->logger, LOGGER_ERR, "RAOP not initialized at SETUP, playing will fail!");
http_response_set_disconnect(response, 1);
}
plist_t res_stream_node = plist_new_dict();
plist_t res_stream_data_port_node = plist_new_uint(dport);
plist_t res_stream_control_port_node = plist_new_uint(cport);
plist_t res_stream_type_node = plist_new_uint(96);
plist_dict_set_item(res_stream_node, "dataPort", res_stream_data_port_node);
plist_dict_set_item(res_stream_node, "controlPort", res_stream_control_port_node);
plist_dict_set_item(res_stream_node, "type", res_stream_type_node);
plist_array_append_item(res_streams_node, res_stream_node);
break;
}
default:
logger_log(conn->raop->logger, LOGGER_ERR, "SETUP tries to setup stream of unknown type %llu", type);
http_response_set_disconnect(response, 1);
break;
}
}
plist_dict_set_item(res_root_node, "streams", res_streams_node);
}
plist_to_bin(res_root_node, response_data, (uint32_t*) response_datalen);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
}
static void
raop_handler_get_parameter(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
const char *content_type;
const char *data;
int datalen;
content_type = http_request_get_header(request, "Content-Type");
data = http_request_get_data(request, &datalen);
if (!strcmp(content_type, "text/parameters")) {
const char *current = data;
while (current) {
const char *next;
int handled = 0;
/* This is a bit ugly, but seems to be how airport works too */
if (!strncmp(current, "volume\r\n", 8)) {
const char volume[] = "volume: 0.0\r\n";
http_response_add_header(response, "Content-Type", "text/parameters");
*response_data = strdup(volume);
if (*response_data) {
*response_datalen = strlen(*response_data);
}
handled = 1;
}
next = strstr(current, "\r\n");
if (next && !handled) {
logger_log(conn->raop->logger, LOGGER_WARNING,
"Found an unknown parameter: %.*s", (next - current), current);
current = next + 2;
} else if (next) {
current = next + 2;
} else {
current = NULL;
}
}
}
}
static void
raop_handler_set_parameter(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
const char *content_type;
const char *data;
int datalen;
content_type = http_request_get_header(request, "Content-Type");
data = http_request_get_data(request, &datalen);
if (!strcmp(content_type, "text/parameters")) {
char *datastr;
datastr = calloc(1, datalen+1);
if (data && datastr && conn->raop_rtp) {
memcpy(datastr, data, datalen);
if (!strncmp(datastr, "volume: ", 8)) {
float vol = 0.0;
sscanf(datastr+8, "%f", &vol);
raop_rtp_set_volume(conn->raop_rtp, vol);
} else if (!strncmp(datastr, "progress: ", 10)) {
unsigned int start, curr, end;
sscanf(datastr+10, "%u/%u/%u", &start, &curr, &end);
raop_rtp_set_progress(conn->raop_rtp, start, curr, end);
}
} else if (!conn->raop_rtp) {
logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER");
}
free(datastr);
} else if (!strcmp(content_type, "image/jpeg") || !strcmp(content_type, "image/png")) {
logger_log(conn->raop->logger, LOGGER_INFO, "Got image data of %d bytes", datalen);
if (conn->raop_rtp) {
raop_rtp_set_coverart(conn->raop_rtp, data, datalen);
} else {
logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER coverart");
}
} else if (!strcmp(content_type, "application/x-dmap-tagged")) {
logger_log(conn->raop->logger, LOGGER_INFO, "Got metadata of %d bytes", datalen);
if (conn->raop_rtp) {
raop_rtp_set_metadata(conn->raop_rtp, data, datalen);
} else {
logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER metadata");
}
}
}
static void
raop_handler_feedback(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
logger_log(conn->raop->logger, LOGGER_DEBUG, "raop_handler_feedback");
}
static void
raop_handler_record(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen)
{
logger_log(conn->raop->logger, LOGGER_DEBUG, "raop_handler_record");
http_response_add_header(response, "Audio-Latency", "11025");
http_response_add_header(response, "Audio-Jack-Status", "connected; type=analog");
}