mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-14 08:15:02 -05:00
[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:
parent
e3ce003190
commit
a29772e8be
@ -161,6 +161,8 @@ struct raop_extra
|
|||||||
|
|
||||||
bool encrypt;
|
bool encrypt;
|
||||||
bool wants_metadata;
|
bool wants_metadata;
|
||||||
|
|
||||||
|
char *pk;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct raop_session
|
struct raop_session
|
||||||
@ -172,6 +174,7 @@ struct raop_session
|
|||||||
bool encrypt;
|
bool encrypt;
|
||||||
bool auth_quirk_itunes;
|
bool auth_quirk_itunes;
|
||||||
bool wants_metadata;
|
bool wants_metadata;
|
||||||
|
bool supports_post;
|
||||||
|
|
||||||
bool only_probe;
|
bool only_probe;
|
||||||
|
|
||||||
@ -185,6 +188,7 @@ struct raop_session
|
|||||||
char *realm;
|
char *realm;
|
||||||
char *nonce;
|
char *nonce;
|
||||||
const char *password;
|
const char *password;
|
||||||
|
const char *pk;
|
||||||
|
|
||||||
char *devname;
|
char *devname;
|
||||||
char *address;
|
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_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 */
|
/* Keep in sync with enum raop_devtype */
|
||||||
static const char *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);
|
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);
|
snprintf(buf, sizeof(buf), "%d", rs->cseq);
|
||||||
evrtsp_add_header(req->output_headers, "CSeq", buf);
|
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
|
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;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "%s: Sending TEARDOWN to '%s'\n", log_caller, rs->devname);
|
||||||
|
|
||||||
req = evrtsp_request_new(cb, rs);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_TEARDOWN, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1352,12 +1361,14 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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];
|
char buf[64];
|
||||||
struct evrtsp_request *req;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "%s: Sending FLUSH to '%s'\n", log_caller, rs->devname);
|
||||||
|
|
||||||
req = evrtsp_request_new(cb, rs);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_FLUSH, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1400,11 +1411,13 @@ raop_send_req_flush(struct raop_session *rs, uint64_t rtptime, evrtsp_req_cb cb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "%s: Sending SET_PARAMETER to '%s'\n", log_caller, rs->devname);
|
||||||
|
|
||||||
req = evrtsp_request_new(cb, rs);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_SET_PARAMETER, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1450,12 +1463,14 @@ raop_send_req_set_parameter(struct raop_session *rs, struct evbuffer *evbuf, cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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];
|
char buf[64];
|
||||||
struct evrtsp_request *req;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "%s: Sending RECORD to '%s'\n", log_caller, rs->devname);
|
||||||
|
|
||||||
req = evrtsp_request_new(cb, rs);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_RECORD, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1498,12 +1513,14 @@ raop_send_req_record(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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];
|
char hdr[128];
|
||||||
struct evrtsp_request *req;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "%s: Sending SETUP to '%s'\n", log_caller, rs->devname);
|
||||||
|
|
||||||
req = evrtsp_request_new(cb, rs);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_SETUP, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1546,7 +1563,7 @@ raop_send_req_setup(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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];
|
uint8_t challenge[16];
|
||||||
char *challenge_b64;
|
char *challenge_b64;
|
||||||
@ -1559,6 +1576,8 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
uint32_t session_id;
|
uint32_t session_id;
|
||||||
int ret;
|
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 */
|
/* Determine local address, needed for SDP and session URL */
|
||||||
evrtsp_connection_get_local_address(rs->ctrl, &address, &port, &family);
|
evrtsp_connection_get_local_address(rs->ctrl, &address, &port, &family);
|
||||||
if (!address || (port == 0))
|
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);
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_ANNOUNCE, rs->session_url);
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1660,12 +1679,80 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
return -1;
|
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
|
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;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
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);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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, "*");
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_OPTIONS, "*");
|
||||||
if (ret < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1696,16 +1783,17 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
|
|
||||||
#ifdef RAOP_VERIFICATION
|
#ifdef RAOP_VERIFICATION
|
||||||
static int
|
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;
|
struct evrtsp_request *req;
|
||||||
int ret;
|
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);
|
req = evrtsp_request_new(cb, rs);
|
||||||
if (!req)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1716,13 +1804,12 @@ raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
return -1;
|
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");
|
ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, "/pair-pin-start");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not make pair-pin-start request\n");
|
DPRINTF(E_LOG, L_RAOP, "Could not make pair-pin-start request\n");
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1734,10 +1821,9 @@ raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static int
|
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);
|
DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1957,6 +2043,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb, boo
|
|||||||
|
|
||||||
rs->password = rd->password;
|
rs->password = rd->password;
|
||||||
|
|
||||||
|
rs->pk = re->pk;
|
||||||
rs->wants_metadata = re->wants_metadata;
|
rs->wants_metadata = re->wants_metadata;
|
||||||
|
|
||||||
switch (re->devtype)
|
switch (re->devtype)
|
||||||
@ -2103,11 +2190,7 @@ raop_cb_metadata(struct evrtsp_request *req, void *arg)
|
|||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (req->response_code != RTSP_OK)
|
if (req->response_code != RTSP_OK)
|
||||||
{
|
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);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = raop_check_cseq(rs, req);
|
ret = raop_check_cseq(rs, req);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -2154,9 +2237,9 @@ raop_metadata_send_progress(struct raop_session *rs, struct evbuffer *evbuf, str
|
|||||||
return -1;
|
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)
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -2196,9 +2279,9 @@ raop_metadata_send_artwork(struct raop_session *rs, struct evbuffer *evbuf, stru
|
|||||||
return -1;
|
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)
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -2221,9 +2304,9 @@ raop_metadata_send_metadata(struct raop_session *rs, struct evbuffer *evbuf, str
|
|||||||
return -1;
|
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)
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -2303,8 +2386,8 @@ raop_metadata_startup_send(struct raop_session *rs)
|
|||||||
sent = 0;
|
sent = 0;
|
||||||
for (rmd = metadata_head; rmd; rmd = rmd->next)
|
for (rmd = metadata_head; rmd; rmd = rmd->next)
|
||||||
{
|
{
|
||||||
/* Current song */
|
// Current song, rmd->start >= rmd->end if endless stream
|
||||||
if ((rs->start_rtptime >= rmd->start) && (rs->start_rtptime < rmd->end))
|
if ((rs->start_rtptime >= rmd->start) && ( (rs->start_rtptime < rmd->end) || (rmd->start >= rmd->end) ))
|
||||||
{
|
{
|
||||||
offset = rs->start_rtptime - rmd->start;
|
offset = rs->start_rtptime - rmd->start;
|
||||||
|
|
||||||
@ -2318,7 +2401,7 @@ raop_metadata_startup_send(struct raop_session *rs)
|
|||||||
|
|
||||||
sent = 1;
|
sent = 1;
|
||||||
}
|
}
|
||||||
/* Next song(s) */
|
// Next song(s)
|
||||||
else if (sent && (rs->start_rtptime < rmd->start))
|
else if (sent && (rs->start_rtptime < rmd->start))
|
||||||
{
|
{
|
||||||
ret = raop_metadata_send_internal(rs, rmd, 0, RAOP_MD_DELAY_SWITCH);
|
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;
|
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)
|
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);
|
evbuffer_free(evbuf);
|
||||||
|
|
||||||
@ -2496,7 +2579,7 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg)
|
|||||||
|
|
||||||
if (req->response_code != RTSP_OK)
|
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;
|
goto error;
|
||||||
}
|
}
|
||||||
@ -2558,7 +2641,7 @@ raop_cb_flush(struct evrtsp_request *req, void *arg)
|
|||||||
|
|
||||||
if (req->response_code != RTSP_OK)
|
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;
|
goto error;
|
||||||
}
|
}
|
||||||
@ -2674,7 +2757,7 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
|
|||||||
if (!(rs->state & RAOP_STATE_F_CONNECTED))
|
if (!(rs->state & RAOP_STATE_F_CONNECTED))
|
||||||
continue;
|
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)
|
if (rs->state != RAOP_STATE_STREAMING)
|
||||||
continue;
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
raop_session_failure(rs);
|
raop_session_failure(rs);
|
||||||
@ -3617,7 +3700,7 @@ raop_startup_cancel(struct raop_session *rs)
|
|||||||
{
|
{
|
||||||
/* Try being nice to our peer */
|
/* Try being nice to our peer */
|
||||||
if (rs->session)
|
if (rs->session)
|
||||||
raop_send_req_teardown(rs, raop_session_failure_cb);
|
raop_send_req_teardown(rs, raop_session_failure_cb, "startup_cancel");
|
||||||
else
|
else
|
||||||
raop_session_failure(rs);
|
raop_session_failure(rs);
|
||||||
}
|
}
|
||||||
@ -3845,7 +3928,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
|
|||||||
rs->state = RAOP_STATE_SETUP;
|
rs->state = RAOP_STATE_SETUP;
|
||||||
|
|
||||||
/* Send RECORD */
|
/* 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)
|
if (ret < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
@ -3880,7 +3963,32 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg)
|
|||||||
rs->state = RAOP_STATE_ANNOUNCE;
|
rs->state = RAOP_STATE_ANNOUNCE;
|
||||||
|
|
||||||
/* Send SETUP */
|
/* 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)
|
if (ret < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
@ -3894,6 +4002,7 @@ static void
|
|||||||
raop_cb_startup_options(struct evrtsp_request *req, void *arg)
|
raop_cb_startup_options(struct evrtsp_request *req, void *arg)
|
||||||
{
|
{
|
||||||
struct raop_session *rs = arg;
|
struct raop_session *rs = arg;
|
||||||
|
const char *param;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
rs->reqs_in_flight--;
|
rs->reqs_in_flight--;
|
||||||
@ -3938,7 +4047,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto cleanup;
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n");
|
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;
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not request PIN for device verification\n");
|
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;
|
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)
|
if (rs->only_probe)
|
||||||
{
|
{
|
||||||
/* Device probed successfully, tell our user */
|
// Device probed successfully, tell our user
|
||||||
raop_status(rs);
|
raop_status(rs);
|
||||||
|
|
||||||
/* We're not going further with this session */
|
// We're not going further with this session
|
||||||
raop_session_cleanup(rs);
|
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
|
else
|
||||||
{
|
{
|
||||||
/* Send ANNOUNCE */
|
// Send ANNOUNCE
|
||||||
ret = raop_send_req_announce(rs, raop_cb_startup_announce);
|
ret = raop_send_req_announce(rs, raop_cb_startup_announce, "startup_options");
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
@ -4195,7 +4317,7 @@ raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg)
|
|||||||
|
|
||||||
rs->state = RAOP_STATE_STARTUP;
|
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();
|
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"]
|
["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:
|
* 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" "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:
|
* 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"]
|
["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:
|
* AirFoil:
|
||||||
@ -4594,6 +4717,10 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
else
|
else
|
||||||
re->wants_metadata = 0;
|
re->wants_metadata = 0;
|
||||||
|
|
||||||
|
p = keyval_get(txt, "pk");
|
||||||
|
if (p)
|
||||||
|
re->pk = strdup(p);
|
||||||
|
|
||||||
rd->advertised = 1;
|
rd->advertised = 1;
|
||||||
|
|
||||||
switch (family)
|
switch (family)
|
||||||
@ -4646,9 +4773,9 @@ raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_
|
|||||||
if (rd->auth_key)
|
if (rd->auth_key)
|
||||||
ret = raop_verification_verify(rs);
|
ret = raop_verification_verify(rs);
|
||||||
else if (rd->requires_auth)
|
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
|
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)
|
if (ret == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@ -4668,9 +4795,9 @@ raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_
|
|||||||
if (rd->auth_key)
|
if (rd->auth_key)
|
||||||
ret = raop_verification_verify(rs);
|
ret = raop_verification_verify(rs);
|
||||||
else if (rd->requires_auth)
|
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
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -4699,10 +4826,10 @@ raop_device_stop(struct output_session *session)
|
|||||||
{
|
{
|
||||||
struct raop_session *rs = session->session;
|
struct raop_session *rs = session->session;
|
||||||
|
|
||||||
if (!(rs->state & RAOP_STATE_F_CONNECTED))
|
if (rs->state & RAOP_STATE_F_CONNECTED)
|
||||||
raop_session_cleanup(rs);
|
raop_send_req_teardown(rs, raop_cb_shutdown_teardown, "device_stop");
|
||||||
else
|
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;
|
struct raop_extra *re = device->extra_device_info;
|
||||||
|
|
||||||
|
free(re->pk);
|
||||||
free(re);
|
free(re);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4744,9 +4872,9 @@ raop_playback_stop(void)
|
|||||||
|
|
||||||
for (rs = sessions; rs; rs = rs->next)
|
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)
|
if (ret < 0)
|
||||||
DPRINTF(E_LOG, L_RAOP, "shutdown: TEARDOWN request failed!\n");
|
DPRINTF(E_LOG, L_RAOP, "playback_stop: TEARDOWN request failed!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user