[raop] Fix AirPlay 2 issue #557, fix for stream metadata, better logging

AirPlay 2 devices like Sonos One and AirPort Express with firmware 7.8
require auth-setup before ANNOUNCE, otherwise they will return 403.

Also fixed a problem where metadata did not get sent when toggling
a speaker on/off if we were playing an endless stream.
This commit is contained in:
ejurgensen 2018-09-02 20:17:18 +02:00
parent e3ce003190
commit a29772e8be

View File

@ -161,6 +161,8 @@ struct raop_extra
bool encrypt;
bool wants_metadata;
char *pk;
};
struct raop_session
@ -172,6 +174,7 @@ struct raop_session
bool encrypt;
bool auth_quirk_itunes;
bool wants_metadata;
bool supports_post;
bool only_probe;
@ -185,6 +188,7 @@ struct raop_session
char *realm;
char *nonce;
const char *password;
const char *pk;
char *devname;
char *address;
@ -276,6 +280,11 @@ static const uint8_t raop_rsa_pubkey[] =
static const uint8_t raop_rsa_exp[] = "\x01\x00\x01";
static const uint8_t raop_auth_setup_pubkey[] =
"\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07"
"\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e";
/* Keep in sync with enum raop_devtype */
static const char *raop_devtype[] =
{
@ -1171,8 +1180,6 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts
method = evrtsp_method(req_method);
DPRINTF(E_DBG, L_RAOP, "Building %s for '%s'\n", method, rs->devname);
snprintf(buf, sizeof(buf), "%d", rs->cseq);
evrtsp_add_header(req->output_headers, "CSeq", buf);
@ -1315,11 +1322,13 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address
*/
static int
raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending TEARDOWN to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1338,7 +1347,7 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_TEARDOWN, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make TEARDOWN request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make TEARDOWN request to '%s'\n", rs->devname);
return -1;
}
@ -1352,12 +1361,14 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb)
}
static int
raop_send_req_flush(struct raop_session *rs, uint64_t rtptime, evrtsp_req_cb cb)
raop_send_req_flush(struct raop_session *rs, uint64_t rtptime, evrtsp_req_cb cb, const char *log_caller)
{
char buf[64];
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending FLUSH to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1387,7 +1398,7 @@ raop_send_req_flush(struct raop_session *rs, uint64_t rtptime, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_FLUSH, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make FLUSH request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make FLUSH request to '%s'\n", rs->devname);
return -1;
}
@ -1400,11 +1411,13 @@ raop_send_req_flush(struct raop_session *rs, uint64_t rtptime, evrtsp_req_cb cb)
}
static int
raop_send_req_set_parameter(struct raop_session *rs, struct evbuffer *evbuf, char *ctype, char *rtpinfo, evrtsp_req_cb cb)
raop_send_req_set_parameter(struct raop_session *rs, struct evbuffer *evbuf, char *ctype, char *rtpinfo, evrtsp_req_cb cb, const char *log_caller)
{
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending SET_PARAMETER to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1437,7 +1450,7 @@ raop_send_req_set_parameter(struct raop_session *rs, struct evbuffer *evbuf, cha
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_SET_PARAMETER, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make SET_PARAMETER request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make SET_PARAMETER request to '%s'\n", rs->devname);
return -1;
}
@ -1450,12 +1463,14 @@ raop_send_req_set_parameter(struct raop_session *rs, struct evbuffer *evbuf, cha
}
static int
raop_send_req_record(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_record(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
char buf[64];
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending RECORD to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1487,7 +1502,7 @@ raop_send_req_record(struct raop_session *rs, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_RECORD, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make RECORD request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make RECORD request to '%s'\n", rs->devname);
return -1;
}
@ -1498,12 +1513,14 @@ raop_send_req_record(struct raop_session *rs, evrtsp_req_cb cb)
}
static int
raop_send_req_setup(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_setup(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
char hdr[128];
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending SETUP to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1535,7 +1552,7 @@ raop_send_req_setup(struct raop_session *rs, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_SETUP, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make SETUP request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make SETUP request to '%s'\n", rs->devname);
return -1;
}
@ -1546,7 +1563,7 @@ raop_send_req_setup(struct raop_session *rs, evrtsp_req_cb cb)
}
static int
raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
uint8_t challenge[16];
char *challenge_b64;
@ -1559,6 +1576,8 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
uint32_t session_id;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending ANNOUNCE to '%s'\n", log_caller, rs->devname);
/* Determine local address, needed for SDP and session URL */
evrtsp_connection_get_local_address(rs->ctrl, &address, &port, &family);
if (!address || (port == 0))
@ -1645,7 +1664,7 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_ANNOUNCE, rs->session_url);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make ANNOUNCE request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make ANNOUNCE request to '%s'\n", rs->devname);
return -1;
}
@ -1660,12 +1679,80 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
return -1;
}
/*
The purpose of auth-setup is to authenticate the device and to exchange keys
for encryption. We don't do that, but some AirPlay 2 speakers (Sonos beam,
Airport Express fw 7.8) require this step anyway, otherwise we get a 403 to
our ANNOUNCE. So we do it with a flag for no encryption, and without actually
authenticating the device.
Good to know (source Apple's MFi Accessory Interface Specification):
- Curve25519 Elliptic-Curve Diffie-Hellman technology for key exchange
- RSA for signing and verifying and AES-128 in counter mode for encryption
- We start by sending a Curve25519 public key + no encryption flag
- The device responds with public key, MFi certificate and a signature, which
is created by the device signing the two public keys with its RSA private
key and then encrypting the result with the AES master key derived from the
Curve25519 shared secret (generated from device private key and our public
key)
- The AES key derived from the Curve25519 shared secret can then be used to
encrypt future content
- New keys should be generated for each authentication attempt, but we don't
do that because we don't really use this + it adds a libsodium dependency
Since we don't do auth or encryption, we currently just ignore the reponse.
*/
static int
raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_auth_setup(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending auth-setup to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for auth-setup\n");
return -1;
}
ret = raop_add_headers(rs, req, EVRTSP_REQ_POST);
if (ret < 0)
{
evrtsp_request_free(req);
return -1;
}
evrtsp_add_header(req->output_headers, "Content-Type", "application/octet-stream");
// Flag for no encryption. 0x10 may mean encryption.
evbuffer_add(req->output_buffer, "\x01", 1);
evbuffer_add(req->output_buffer, raop_auth_setup_pubkey, sizeof(raop_auth_setup_pubkey) - 1);
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, "/auth-setup");
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make auth-setup request to '%s'\n", rs->devname);
return -1;
}
rs->reqs_in_flight++;
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
return 0;
}
static int
raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending OPTIONS to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
@ -1683,7 +1770,7 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_OPTIONS, "*");
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make OPTIONS request\n");
DPRINTF(E_LOG, L_RAOP, "Could not make OPTIONS request to '%s'\n", rs->devname);
return -1;
}
@ -1696,16 +1783,17 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
#ifdef RAOP_VERIFICATION
static int
raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
struct evrtsp_request *req;
int ret;
DPRINTF(E_DBG, L_RAOP, "%s: Sending pair-pin-start to '%s'\n", log_caller, rs->devname);
req = evrtsp_request_new(cb, rs);
if (!req)
{
DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for pair-pin-start\n");
DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request to '%s' for pair-pin-start\n", rs->devname);
return -1;
}
@ -1716,13 +1804,12 @@ raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
return -1;
}
DPRINTF(E_LOG, L_RAOP, "Starting device verification for '%s', please submit PIN via a *.verification file\n", rs->devname);
DPRINTF(E_LOG, L_RAOP, "Starting device verification for '%s', go to the web interface and enter PIN\n", rs->devname);
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, "/pair-pin-start");
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not make pair-pin-start request\n");
return -1;
}
@ -1734,10 +1821,9 @@ raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
}
#else
static int
raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb, const char *log_caller)
{
DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname);
return -1;
}
#endif
@ -1957,6 +2043,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb, boo
rs->password = rd->password;
rs->pk = re->pk;
rs->wants_metadata = re->wants_metadata;
switch (re->devtype)
@ -2103,11 +2190,7 @@ raop_cb_metadata(struct evrtsp_request *req, void *arg)
goto error;
if (req->response_code != RTSP_OK)
{
DPRINTF(E_LOG, L_RAOP, "SET_PARAMETER request failed for metadata/artwork/progress: %d %s\n", req->response_code, req->response_code_line);
goto error;
}
DPRINTF(E_WARN, L_RAOP, "SET_PARAMETER metadata/artwork/progress request to '%s' failed (proceeding anyway): %d %s\n", rs->devname, req->response_code, req->response_code_line);
ret = raop_check_cseq(rs, req);
if (ret < 0)
@ -2154,9 +2237,9 @@ raop_metadata_send_progress(struct raop_session *rs, struct evbuffer *evbuf, str
return -1;
}
ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, raop_cb_metadata);
ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, raop_cb_metadata, "send_progress");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER request for metadata\n");
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER progress request to '%s'\n", rs->devname);
return ret;
}
@ -2196,9 +2279,9 @@ raop_metadata_send_artwork(struct raop_session *rs, struct evbuffer *evbuf, stru
return -1;
}
ret = raop_send_req_set_parameter(rs, evbuf, ctype, rtptime, raop_cb_metadata);
ret = raop_send_req_set_parameter(rs, evbuf, ctype, rtptime, raop_cb_metadata, "send_artwork");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER request for metadata\n");
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER artwork request to '%s'\n", rs->devname);
return ret;
}
@ -2221,9 +2304,9 @@ raop_metadata_send_metadata(struct raop_session *rs, struct evbuffer *evbuf, str
return -1;
}
ret = raop_send_req_set_parameter(rs, evbuf, "application/x-dmap-tagged", rtptime, raop_cb_metadata);
ret = raop_send_req_set_parameter(rs, evbuf, "application/x-dmap-tagged", rtptime, raop_cb_metadata, "send_metadata");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER request for metadata\n");
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER metadata request to '%s'\n", rs->devname);
return ret;
}
@ -2303,8 +2386,8 @@ raop_metadata_startup_send(struct raop_session *rs)
sent = 0;
for (rmd = metadata_head; rmd; rmd = rmd->next)
{
/* Current song */
if ((rs->start_rtptime >= rmd->start) && (rs->start_rtptime < rmd->end))
// Current song, rmd->start >= rmd->end if endless stream
if ((rs->start_rtptime >= rmd->start) && ( (rs->start_rtptime < rmd->end) || (rmd->start >= rmd->end) ))
{
offset = rs->start_rtptime - rmd->start;
@ -2318,7 +2401,7 @@ raop_metadata_startup_send(struct raop_session *rs)
sent = 1;
}
/* Next song(s) */
// Next song(s)
else if (sent && (rs->start_rtptime < rmd->start))
{
ret = raop_metadata_send_internal(rs, rmd, 0, RAOP_MD_DELAY_SWITCH);
@ -2472,9 +2555,9 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
return -1;
}
ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, cb);
ret = raop_send_req_set_parameter(rs, evbuf, "text/parameters", NULL, cb, "volume_internal");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER request for volume\n");
DPRINTF(E_LOG, L_RAOP, "Could not send SET_PARAMETER request for volume to '%s'\n", rs->devname);
evbuffer_free(evbuf);
@ -2496,7 +2579,7 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg)
if (req->response_code != RTSP_OK)
{
DPRINTF(E_LOG, L_RAOP, "SET_PARAMETER request failed for stream volume: %d %s\n", req->response_code, req->response_code_line);
DPRINTF(E_LOG, L_RAOP, "SET_PARAMETER request to '%s' failed for stream volume: %d %s\n", rs->devname, req->response_code, req->response_code_line);
goto error;
}
@ -2558,7 +2641,7 @@ raop_cb_flush(struct evrtsp_request *req, void *arg)
if (req->response_code != RTSP_OK)
{
DPRINTF(E_LOG, L_RAOP, "FLUSH request failed: %d %s\n", req->response_code, req->response_code_line);
DPRINTF(E_LOG, L_RAOP, "FLUSH request to '%s' failed: %d %s\n", rs->devname, req->response_code, req->response_code_line);
goto error;
}
@ -2674,7 +2757,7 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
if (!(rs->state & RAOP_STATE_F_CONNECTED))
continue;
raop_send_req_options(rs, raop_cb_keep_alive);
raop_send_req_options(rs, raop_cb_keep_alive, "keep_alive");
}
}
@ -2695,7 +2778,7 @@ raop_flush(output_status_cb cb, uint64_t rtptime)
if (rs->state != RAOP_STATE_STREAMING)
continue;
ret = raop_send_req_flush(rs, rtptime, raop_cb_flush);
ret = raop_send_req_flush(rs, rtptime, raop_cb_flush, "flush");
if (ret < 0)
{
raop_session_failure(rs);
@ -3617,7 +3700,7 @@ raop_startup_cancel(struct raop_session *rs)
{
/* Try being nice to our peer */
if (rs->session)
raop_send_req_teardown(rs, raop_session_failure_cb);
raop_send_req_teardown(rs, raop_session_failure_cb, "startup_cancel");
else
raop_session_failure(rs);
}
@ -3845,7 +3928,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
rs->state = RAOP_STATE_SETUP;
/* Send RECORD */
ret = raop_send_req_record(rs, raop_cb_startup_record);
ret = raop_send_req_record(rs, raop_cb_startup_record, "startup_setup");
if (ret < 0)
goto cleanup;
@ -3880,7 +3963,32 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg)
rs->state = RAOP_STATE_ANNOUNCE;
/* Send SETUP */
ret = raop_send_req_setup(rs, raop_cb_startup_setup);
ret = raop_send_req_setup(rs, raop_cb_startup_setup, "startup_announce");
if (ret < 0)
goto cleanup;
return;
cleanup:
raop_startup_cancel(rs);
}
static void
raop_cb_startup_auth_setup(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
int ret;
rs->reqs_in_flight--;
if (!req)
goto cleanup;
if (req->response_code != RTSP_OK)
DPRINTF(E_WARN, L_RAOP, "Unexpected reply to auth-setup from '%s', proceeding anyway (%d %s)\n", rs->devname, req->response_code, req->response_code_line);
// Send ANNOUNCE
ret = raop_send_req_announce(rs, raop_cb_startup_announce, "startup_auth_setup");
if (ret < 0)
goto cleanup;
@ -3894,6 +4002,7 @@ static void
raop_cb_startup_options(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
const char *param;
int ret;
rs->reqs_in_flight--;
@ -3938,7 +4047,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
if (ret < 0)
goto cleanup;
ret = raop_send_req_options(rs, raop_cb_startup_options);
ret = raop_send_req_options(rs, raop_cb_startup_options, "startup_options");
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n");
@ -3952,7 +4061,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
{
rs->device->requires_auth = 1;
ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
ret = raop_send_req_pin_start(rs, raop_cb_pin_start, "startup_options");
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not request PIN for device verification\n");
@ -3964,18 +4073,31 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
rs->state = RAOP_STATE_OPTIONS;
param = evrtsp_find_header(req->input_headers, "Public");
if (param)
rs->supports_post = (strstr(param, "POST") != NULL);
else
DPRINTF(E_DBG, L_RAOP, "Could not find 'Public' header in OPTIONS reply from '%s'\n", rs->devname);
if (rs->only_probe)
{
/* Device probed successfully, tell our user */
// Device probed successfully, tell our user
raop_status(rs);
/* We're not going further with this session */
// We're not going further with this session
raop_session_cleanup(rs);
}
else if (rs->supports_post && rs->pk)
{
// AirPlay 2 devices require this step or the ANNOUNCE will get a 403
ret = raop_send_req_auth_setup(rs, raop_cb_startup_auth_setup, "startup_options");
if (ret < 0)
goto cleanup;
}
else
{
/* Send ANNOUNCE */
ret = raop_send_req_announce(rs, raop_cb_startup_announce);
// Send ANNOUNCE
ret = raop_send_req_announce(rs, raop_cb_startup_announce, "startup_options");
if (ret < 0)
goto cleanup;
}
@ -4195,7 +4317,7 @@ raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg)
rs->state = RAOP_STATE_STARTUP;
raop_send_req_options(rs, raop_cb_startup_options);
raop_send_req_options(rs, raop_cb_startup_options, "verify_step2");
player_speaker_status_trigger();
@ -4396,6 +4518,7 @@ raop_verification_verify(struct raop_session *rs)
["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
* Apple TV 4:
["vv=2" "vs=301.44.3" "vn=65537" "tp=UDP" "pk=9...f" "am=AppleTV5,3" "md=0,1,2" "sf=0x44" "ft=0x5A7FFFF7,0x4DE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
["vv=2" "ov=11.4.1" "vs=366.75.2" "vn=65537" "tp=UDP" "pk=c...8" "am=AppleTV5,3" "md=0,1,2" "sf=0x10244" "ft=0x5A7FFFF7,0x155FDE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
* Sony STR-DN1040:
["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"]
* AirFoil:
@ -4594,6 +4717,10 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
else
re->wants_metadata = 0;
p = keyval_get(txt, "pk");
if (p)
re->pk = strdup(p);
rd->advertised = 1;
switch (family)
@ -4646,9 +4773,9 @@ raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_
if (rd->auth_key)
ret = raop_verification_verify(rs);
else if (rd->requires_auth)
ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
ret = raop_send_req_pin_start(rs, raop_cb_pin_start, "device_start");
else
ret = raop_send_req_options(rs, raop_cb_startup_options);
ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start");
if (ret == 0)
return 0;
@ -4668,9 +4795,9 @@ raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_
if (rd->auth_key)
ret = raop_verification_verify(rs);
else if (rd->requires_auth)
ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
ret = raop_send_req_pin_start(rs, raop_cb_pin_start, "device_start");
else
ret = raop_send_req_options(rs, raop_cb_startup_options);
ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start");
if (ret < 0)
{
@ -4699,10 +4826,10 @@ raop_device_stop(struct output_session *session)
{
struct raop_session *rs = session->session;
if (!(rs->state & RAOP_STATE_F_CONNECTED))
raop_session_cleanup(rs);
if (rs->state & RAOP_STATE_F_CONNECTED)
raop_send_req_teardown(rs, raop_cb_shutdown_teardown, "device_stop");
else
raop_send_req_teardown(rs, raop_cb_shutdown_teardown);
raop_session_cleanup(rs);
}
@ -4711,6 +4838,7 @@ raop_device_free_extra(struct output_device *device)
{
struct raop_extra *re = device->extra_device_info;
free(re->pk);
free(re);
}
@ -4744,9 +4872,9 @@ raop_playback_stop(void)
for (rs = sessions; rs; rs = rs->next)
{
ret = raop_send_req_teardown(rs, raop_cb_shutdown_teardown);
ret = raop_send_req_teardown(rs, raop_cb_shutdown_teardown, "playback_stop");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "shutdown: TEARDOWN request failed!\n");
DPRINTF(E_LOG, L_RAOP, "playback_stop: TEARDOWN request failed!\n");
}
}