2010-05-02 05:32:16 -04:00
/*
2017-06-19 15:52:01 -04:00
* Copyright ( C ) 2012 - 2017 Espen Jürgensen < espenjurgensen @ gmail . com >
2011-03-11 12:32:46 -05:00
* Copyright ( C ) 2010 - 2011 Julien BLACHE < jb @ jblache . org >
2010-05-02 05:32:16 -04:00
*
* RAOP AirTunes v2
*
* Crypto code adapted from VideoLAN
* Copyright ( C ) 2008 the VideoLAN team
* Author : Michael Hanselmann
* GPLv2 +
*
* ALAC encoding adapted from raop_play
* Copyright ( C ) 2005 Shiro Ninomiya < shiron @ snino . com >
* GPLv2 +
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
2017-06-19 15:52:01 -04:00
# include <stdbool.h>
2010-05-02 05:32:16 -04:00
# include <unistd.h>
# include <stdint.h>
# include <inttypes.h>
# include <math.h>
# include <errno.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
# include <fcntl.h>
# include <time.h>
2011-02-23 14:44:49 -05:00
2017-01-06 03:44:18 -05:00
# ifdef HAVE_ENDIAN_H
2011-02-23 14:44:49 -05:00
# include <endian.h>
2017-01-06 03:44:18 -05:00
# elif defined(HAVE_SYS_ENDIAN_H)
2011-02-23 14:44:49 -05:00
# include <sys / endian.h>
2017-01-14 00:56:43 -05:00
# elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
# include <libkern/OSByteOrder.h>
# define htobe16(x) OSSwapHostToBigInt16(x)
# define be16toh(x) OSSwapBigToHostInt16(x)
# define htobe32(x) OSSwapHostToBigInt32(x)
2011-02-23 14:44:49 -05:00
# endif
2010-05-02 05:32:16 -04:00
# include <arpa/inet.h>
2010-07-10 06:27:39 -04:00
# include <net/if.h>
2016-01-23 19:14:07 -05:00
# include <netinet/in.h>
2010-05-02 05:32:16 -04:00
2015-10-19 15:15:29 -04:00
# include <event2/event.h>
# include <event2/buffer.h>
2010-05-02 05:32:16 -04:00
# include <gcrypt.h>
2015-10-19 15:15:29 -04:00
# include "evrtsp/evrtsp.h"
2010-05-02 05:32:16 -04:00
# include "conffile.h"
# include "logger.h"
2016-01-23 19:14:07 -05:00
# include "mdns.h"
2010-05-02 05:32:16 -04:00
# include "misc.h"
# include "player.h"
2011-03-08 16:04:48 -05:00
# include "db.h"
# include "artwork.h"
# include "dmap_common.h"
2016-01-23 19:14:07 -05:00
# include "outputs.h"
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
# ifdef RAOP_VERIFICATION
# include "raop_verification.h"
# endif
2010-05-02 05:32:16 -04:00
# ifndef MIN
# define MIN(a, b) ((a < b) ? a : b)
# endif
2010-07-30 12:34:59 -04:00
# define AIRTUNES_V2_HDR_LEN 12
# define ALAC_HDR_LEN 3
# define AIRTUNES_V2_PKT_LEN (AIRTUNES_V2_HDR_LEN + ALAC_HDR_LEN + STOB(AIRTUNES_V2_PACKET_SAMPLES))
2010-07-30 12:52:14 -04:00
# define AIRTUNES_V2_PKT_TAIL_LEN (AIRTUNES_V2_PKT_LEN - AIRTUNES_V2_HDR_LEN - ((AIRTUNES_V2_PKT_LEN / 16) * 16))
# define AIRTUNES_V2_PKT_TAIL_OFF (AIRTUNES_V2_PKT_LEN - AIRTUNES_V2_PKT_TAIL_LEN)
2010-10-05 14:02:42 -04:00
# define RETRANSMIT_BUFFER_SIZE 1000
2010-05-02 05:32:16 -04:00
2011-03-08 16:04:48 -05:00
# define RAOP_MD_DELAY_STARTUP 15360
# define RAOP_MD_DELAY_SWITCH (RAOP_MD_DELAY_STARTUP * 2)
2010-05-02 05:32:16 -04:00
2014-02-09 10:07:00 -05:00
/* This is an arbitrary value which just needs to be kept in sync with the config */
# define RAOP_CONFIG_MAX_VOLUME 11
2016-01-23 19:14:07 -05:00
union sockaddr_all
{
struct sockaddr_in sin ;
struct sockaddr_in6 sin6 ;
struct sockaddr sa ;
struct sockaddr_storage ss ;
} ;
2010-05-02 05:32:16 -04:00
struct raop_v2_packet
{
2010-07-30 12:52:14 -04:00
uint8_t clear [ AIRTUNES_V2_PKT_LEN ] ;
uint8_t encrypted [ AIRTUNES_V2_PKT_LEN ] ;
2010-10-05 14:02:42 -04:00
uint16_t seqnum ;
struct raop_v2_packet * prev ;
struct raop_v2_packet * next ;
2010-05-02 05:32:16 -04:00
} ;
2016-01-23 19:14:07 -05:00
enum raop_devtype {
RAOP_DEV_APEX1_80211G ,
RAOP_DEV_APEX2_80211N ,
RAOP_DEV_APEX3_80211N ,
RAOP_DEV_APPLETV ,
2016-10-08 18:42:48 -04:00
RAOP_DEV_APPLETV4 ,
2016-01-23 19:14:07 -05:00
RAOP_DEV_OTHER ,
} ;
2016-02-12 15:59:26 -05:00
// Session is starting up
2017-06-20 14:00:05 -04:00
# define RAOP_STATE_F_STARTUP (1 << 13)
2016-02-12 15:59:26 -05:00
// Streaming is up (connection established)
2017-06-20 14:00:05 -04:00
# define RAOP_STATE_F_CONNECTED (1 << 14)
// Couldn't start device
# define RAOP_STATE_F_FAILED (1 << 15)
2016-02-12 15:59:26 -05:00
enum raop_state {
// Device is stopped (no session)
RAOP_STATE_STOPPED = 0 ,
// Session startup
2017-06-20 14:00:05 -04:00
RAOP_STATE_STARTUP = RAOP_STATE_F_STARTUP | 0x01 ,
RAOP_STATE_OPTIONS = RAOP_STATE_F_STARTUP | 0x02 ,
RAOP_STATE_ANNOUNCE = RAOP_STATE_F_STARTUP | 0x03 ,
RAOP_STATE_SETUP = RAOP_STATE_F_STARTUP | 0x04 ,
RAOP_STATE_RECORD = RAOP_STATE_F_STARTUP | 0x05 ,
2016-02-12 15:59:26 -05:00
// Session established
// - streaming ready (RECORD sent and acked, connection established)
// - commands (SET_PARAMETER) are possible
2017-06-20 14:00:05 -04:00
RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01 ,
2016-02-12 15:59:26 -05:00
// Media data is being sent
2017-06-20 14:00:05 -04:00
RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02 ,
2016-02-12 15:59:26 -05:00
// Session is failed, couldn't startup or error occurred
2017-06-20 14:00:05 -04:00
RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01 ,
2016-02-12 15:59:26 -05:00
// Password issue: unknown password or bad password
2017-06-20 14:00:05 -04:00
RAOP_STATE_PASSWORD = RAOP_STATE_F_FAILED | 0x02 ,
2017-06-19 15:52:01 -04:00
// Device requires verification, pending PIN from user
2017-06-20 14:00:05 -04:00
RAOP_STATE_UNVERIFIED = RAOP_STATE_F_FAILED | 0x03 ,
2016-02-12 15:59:26 -05:00
} ;
2016-01-23 19:14:07 -05:00
// Info about the device, which is not required by the player, only internally
struct raop_extra
{
enum raop_devtype devtype ;
2017-06-19 15:52:01 -04:00
bool encrypt ;
bool wants_metadata ;
2016-01-23 19:14:07 -05:00
} ;
2010-05-02 05:32:16 -04:00
struct raop_session
{
struct evrtsp_connection * ctrl ;
2016-02-12 15:59:26 -05:00
enum raop_state state ;
2017-06-19 15:52:01 -04:00
bool req_has_auth ;
bool encrypt ;
bool auth_quirk_itunes ;
bool wants_metadata ;
bool only_probe ;
2010-05-02 05:32:16 -04:00
2015-10-19 15:15:29 -04:00
struct event * deferredev ;
2011-03-11 13:36:51 -05:00
int reqs_in_flight ;
2010-05-02 05:32:16 -04:00
int cseq ;
char * session ;
char session_url [ 128 ] ;
char * realm ;
char * nonce ;
const char * password ;
char * devname ;
char * address ;
2017-05-04 13:31:26 -04:00
int family ;
2010-05-02 05:32:16 -04:00
2010-11-19 15:49:17 -05:00
int volume ;
2010-05-02 05:32:16 -04:00
uint64_t start_rtptime ;
/* Do not dereference - only passed to the status cb */
2016-01-23 19:14:07 -05:00
struct output_device * device ;
struct output_session * output_session ;
output_status_cb status_cb ;
2010-05-02 05:32:16 -04:00
/* AirTunes v2 */
unsigned short server_port ;
unsigned short control_port ;
2017-09-26 15:41:53 -04:00
unsigned short timing_port ; // ATV4 has this set to 0, but it is not used by forked-daapd anyway
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
# ifdef RAOP_VERIFICATION
/* Device verification, see raop_verification.h */
struct verification_verify_context * verification_verify_ctx ;
struct verification_setup_context * verification_setup_ctx ;
# endif
2010-05-02 05:32:16 -04:00
int server_fd ;
union sockaddr_all sa ;
2010-05-14 11:38:05 -04:00
struct raop_service * timing_svc ;
struct raop_service * control_svc ;
2010-05-02 05:32:16 -04:00
struct raop_session * next ;
} ;
2011-03-08 16:04:48 -05:00
struct raop_metadata
{
struct evbuffer * metadata ;
struct evbuffer * artwork ;
int artwork_fmt ;
/* Progress data */
uint64_t start ;
uint64_t end ;
struct raop_metadata * next ;
} ;
2010-05-14 11:38:00 -04:00
struct raop_service
{
int fd ;
unsigned short port ;
2015-10-19 15:15:29 -04:00
struct event * ev ;
2010-05-02 05:32:16 -04:00
} ;
typedef void ( * evrtsp_req_cb ) ( struct evrtsp_request * req , void * arg ) ;
/* Truncate RTP time to lower 32bits for RAOP */
# define RAOP_RTPTIME(x) ((uint32_t)((x) & (uint64_t)0xffffffff))
/* NTP timestamp definitions */
# define FRAC 4294967296. /* 2^32 as a double */
# define NTP_EPOCH_DELTA 0x83aa7e80 /* 2208988800 - that's 1970 - 1900 in seconds */
struct ntp_stamp
{
uint32_t sec ;
uint32_t frac ;
} ;
static const uint8_t raop_rsa_pubkey [ ] =
" \xe7 \xd7 \x44 \xf2 \xa2 \xe2 \x78 \x8b \x6c \x1f \x55 \xa0 \x8e \xb7 \x05 \x44 "
" \xa8 \xfa \x79 \x45 \xaa \x8b \xe6 \xc6 \x2c \xe5 \xf5 \x1c \xbd \xd4 \xdc \x68 "
" \x42 \xfe \x3d \x10 \x83 \xdd \x2e \xde \xc1 \xbf \xd4 \x25 \x2d \xc0 \x2e \x6f "
" \x39 \x8b \xdf \x0e \x61 \x48 \xea \x84 \x85 \x5e \x2e \x44 \x2d \xa6 \xd6 \x26 "
" \x64 \xf6 \x74 \xa1 \xf3 \x04 \x92 \x9a \xde \x4f \x68 \x93 \xef \x2d \xf6 \xe7 "
" \x11 \xa8 \xc7 \x7a \x0d \x91 \xc9 \xd9 \x80 \x82 \x2e \x50 \xd1 \x29 \x22 \xaf "
" \xea \x40 \xea \x9f \x0e \x14 \xc0 \xf7 \x69 \x38 \xc5 \xf3 \x88 \x2f \xc0 \x32 "
" \x3d \xd9 \xfe \x55 \x15 \x5f \x51 \xbb \x59 \x21 \xc2 \x01 \x62 \x9f \xd7 \x33 "
" \x52 \xd5 \xe2 \xef \xaa \xbf \x9b \xa0 \x48 \xd7 \xb8 \x13 \xa2 \xb6 \x76 \x7f "
" \x6c \x3c \xcf \x1e \xb4 \xce \x67 \x3d \x03 \x7b \x0d \x2e \xa3 \x0c \x5f \xff "
" \xeb \x06 \xf8 \xd0 \x8a \xdd \xe4 \x09 \x57 \x1a \x9c \x68 \x9f \xef \x10 \x72 "
" \x88 \x55 \xdd \x8c \xfb \x9a \x8b \xef \x5c \x89 \x43 \xef \x3b \x5f \xaa \x15 "
" \xdd \xe6 \x98 \xbe \xdd \xf3 \x59 \x96 \x03 \xeb \x3e \x6f \x61 \x37 \x2b \xb6 "
" \x28 \xf6 \x55 \x9f \x59 \x9a \x78 \xbf \x50 \x06 \x87 \xaa \x7f \x49 \x76 \xc0 "
" \x56 \x2d \x41 \x29 \x56 \xf8 \x98 \x9e \x18 \xa6 \x35 \x5b \xd8 \x15 \x97 \x82 "
" \x5e \x0f \xc8 \x75 \x34 \x3e \xc7 \x82 \x11 \x76 \x25 \xcd \xbf \x98 \x44 \x7b " ;
static const uint8_t raop_rsa_exp [ ] = " \x01 \x00 \x01 " ;
2016-01-23 19:14:07 -05:00
/* Keep in sync with enum raop_devtype */
static const char * raop_devtype [ ] =
{
" AirPort Express 1 - 802.11g " ,
" AirPort Express 2 - 802.11n " ,
" AirPort Express 3 - 802.11n " ,
" AppleTV " ,
2016-10-08 18:42:48 -04:00
" AppleTV4 " ,
2016-01-23 19:14:07 -05:00
" Other " ,
} ;
2010-05-02 05:32:16 -04:00
/* From player.c */
extern struct event_base * evbase_player ;
/* RAOP AES stream key */
static uint8_t raop_aes_key [ 16 ] ;
static uint8_t raop_aes_iv [ 16 ] ;
static gcry_cipher_hd_t raop_aes_ctx ;
/* Base64-encoded AES key and IV for SDP */
static char * raop_aes_key_b64 ;
static char * raop_aes_iv_b64 ;
/* AirTunes v2 time synchronization */
2010-05-14 11:38:05 -04:00
static struct raop_service timing_4svc ;
static struct raop_service timing_6svc ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:00 -04:00
/* AirTunes v2 playback synchronization / control */
2010-05-14 11:38:05 -04:00
static struct raop_service control_4svc ;
static struct raop_service control_6svc ;
2010-05-02 05:32:16 -04:00
static int sync_counter ;
/* AirTunes v2 audio stream */
2010-10-03 11:01:08 -04:00
static uint32_t ssrc_id ;
2010-05-02 05:32:16 -04:00
static uint16_t stream_seq ;
2010-10-05 14:02:42 -04:00
/* Retransmit packet buffer */
static int pktbuf_size ;
static struct raop_v2_packet * pktbuf_head ;
static struct raop_v2_packet * pktbuf_tail ;
2011-03-08 16:04:48 -05:00
/* Metadata */
static struct raop_metadata * metadata_head ;
static struct raop_metadata * metadata_tail ;
2010-05-02 05:32:16 -04:00
/* FLUSH timer */
2015-10-19 15:15:29 -04:00
static struct event * flush_timer ;
2010-05-02 05:32:16 -04:00
2016-10-08 18:42:48 -04:00
/* Keep-alive timer - hack for ATV's with tvOS 10 */
static struct event * keep_alive_timer ;
2018-04-11 16:29:07 -04:00
static struct timeval keep_alive_tv = { 30 , 0 } ;
2016-10-08 18:42:48 -04:00
2010-05-02 05:32:16 -04:00
/* Sessions */
static struct raop_session * sessions ;
/* ALAC bits writer - big endian
* p outgoing buffer pointer
* val bitfield value
* blen bitfield length , max 8 bits
* bpos bit position in the current byte ( pointed by * p )
*/
static inline void
alac_write_bits ( uint8_t * * p , uint8_t val , int blen , int * bpos )
{
int lb ;
int rb ;
int bd ;
/* Remaining bits in the current byte */
lb = 7 - * bpos + 1 ;
/* Number of bits overflowing */
rb = lb - blen ;
if ( rb > = 0 )
{
bd = val < < rb ;
if ( * bpos = = 0 )
* * p = bd ;
else
* * p | = bd ;
/* No over- nor underflow, we're done with this byte */
if ( rb = = 0 )
{
* p + = 1 ;
* bpos = 0 ;
}
else
* bpos + = blen ;
}
else
{
/* Fill current byte */
bd = val > > - rb ;
* * p | = bd ;
/* Overflow goes to the next byte */
* p + = 1 ;
* * p = val < < ( 8 + rb ) ;
* bpos = - rb ;
}
}
/* Raw data must be little endian */
static void
alac_encode ( uint8_t * raw , uint8_t * buf , int buflen )
{
uint8_t * maxraw ;
int bpos ;
bpos = 0 ;
maxraw = raw + buflen ;
alac_write_bits ( & buf , 1 , 3 , & bpos ) ; /* channel=1, stereo */
alac_write_bits ( & buf , 0 , 4 , & bpos ) ; /* unknown */
alac_write_bits ( & buf , 0 , 8 , & bpos ) ; /* unknown */
alac_write_bits ( & buf , 0 , 4 , & bpos ) ; /* unknown */
alac_write_bits ( & buf , 0 , 1 , & bpos ) ; /* hassize */
alac_write_bits ( & buf , 0 , 2 , & bpos ) ; /* unused */
alac_write_bits ( & buf , 1 , 1 , & bpos ) ; /* is-not-compressed */
for ( ; raw < maxraw ; raw + = 4 )
{
/* Byteswap to big endian */
alac_write_bits ( & buf , * ( raw + 1 ) , 8 , & bpos ) ;
alac_write_bits ( & buf , * raw , 8 , & bpos ) ;
alac_write_bits ( & buf , * ( raw + 3 ) , 8 , & bpos ) ;
alac_write_bits ( & buf , * ( raw + 2 ) , 8 , & bpos ) ;
}
}
/* AirTunes v2 time synchronization helpers */
static inline void
timespec_to_ntp ( struct timespec * ts , struct ntp_stamp * ns )
{
/* Seconds since NTP Epoch (1900-01-01) */
ns - > sec = ts - > tv_sec + NTP_EPOCH_DELTA ;
ns - > frac = ( uint32_t ) ( ( double ) ts - > tv_nsec * 1e-9 * FRAC ) ;
}
static inline void
ntp_to_timespec ( struct ntp_stamp * ns , struct timespec * ts )
{
/* Seconds since Unix Epoch (1970-01-01) */
ts - > tv_sec = ns - > sec - NTP_EPOCH_DELTA ;
ts - > tv_nsec = ( long ) ( ( double ) ns - > frac / ( 1e-9 * FRAC ) ) ;
}
static inline int
raop_v2_timing_get_clock_ntp ( struct ntp_stamp * ns )
{
struct timespec ts ;
int ret ;
ret = clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't get clock: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
timespec_to_ntp ( & ts , ns ) ;
return 0 ;
}
/* RAOP crypto stuff - from VLC */
/* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
* specification .
*/
static int
raop_crypt_mgf1 ( uint8_t * mask , size_t l , const uint8_t * z , const size_t zlen , const int hash )
{
char ebuf [ 64 ] ;
gcry_md_hd_t md_hdl ;
gpg_error_t gc_err ;
uint8_t * md ;
uint32_t counter ;
uint8_t c [ 4 ] ;
size_t copylen ;
int len ;
gc_err = gcry_md_open ( & md_hdl , hash , 0 ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not open hash: %s \n " , ebuf ) ;
return - 1 ;
}
len = gcry_md_get_algo_dlen ( hash ) ;
counter = 0 ;
while ( l > 0 )
{
/* 3. For counter from 0 to \lceil{l / len}\rceil-1, do the following:
* a . Convert counter to an octet string C of length 4 with the
* primitive I2OSP : C = I2OSP ( counter , 4 )
*/
c [ 0 ] = ( counter > > 24 ) & 0xff ;
c [ 1 ] = ( counter > > 16 ) & 0xff ;
c [ 2 ] = ( counter > > 8 ) & 0xff ;
c [ 3 ] = counter & 0xff ;
+ + counter ;
/* b. Concatenate the hash of the seed z and c to the octet string T:
* T = T | | Hash ( Z | | C )
*/
gcry_md_reset ( md_hdl ) ;
gcry_md_write ( md_hdl , z , zlen ) ;
gcry_md_write ( md_hdl , c , 4 ) ;
md = gcry_md_read ( md_hdl , hash ) ;
/* 4. Output the leading l octets of T as the octet string mask. */
copylen = MIN ( l , len ) ;
memcpy ( mask , md , copylen ) ;
mask + = copylen ;
l - = copylen ;
}
gcry_md_close ( md_hdl ) ;
return 0 ;
}
/* EME-OAEP-ENCODE is specified in RFC2437, section 9.1.1.1. Variables are
* named after the specification .
*/
static int
raop_crypt_add_oaep_padding ( uint8_t * em , const size_t emlen , const uint8_t * m , const size_t mlen , const uint8_t * p , const size_t plen )
{
uint8_t * seed ;
uint8_t * db ;
uint8_t * db_mask ;
uint8_t * seed_mask ;
size_t emlen_max ;
size_t pslen ;
size_t i ;
int hlen ;
int ret ;
/* Space for 0x00 prefix in EM. */
emlen_max = emlen - 1 ;
hlen = gcry_md_get_algo_dlen ( GCRY_MD_SHA1 ) ;
/* Step 2:
* If | | M | | > emLen - 2 hLen - 1 then output " message too long " and stop .
*/
if ( mlen > ( emlen_max - ( 2 * hlen ) - 1 ) )
{
DPRINTF ( E_LOG , L_RAOP , " Could not add OAEP padding: message too long \n " ) ;
return - 1 ;
}
/* Step 3:
* Generate an octet string PS consisting of emLen - | | M | | - 2 hLen - 1 zero
* octets . The length of PS may be 0.
*/
pslen = emlen_max - mlen - ( 2 * hlen ) - 1 ;
/*
* Step 5 :
* Concatenate pHash , PS , the message M , and other padding to form a data
* block DB as : DB = pHash | | PS | | 01 | | M
*/
db = calloc ( 1 , hlen + pslen + 1 + mlen ) ;
db_mask = calloc ( 1 , emlen_max - hlen ) ;
seed_mask = calloc ( 1 , hlen ) ;
if ( ! db | | ! db_mask | | ! seed_mask )
{
DPRINTF ( E_LOG , L_RAOP , " Could not allocate memory for OAEP padding \n " ) ;
if ( db )
free ( db ) ;
if ( db_mask )
free ( db_mask ) ;
if ( seed_mask )
free ( seed_mask ) ;
return - 1 ;
}
/* Step 4:
* Let pHash = Hash ( P ) , an octet string of length hLen .
*/
gcry_md_hash_buffer ( GCRY_MD_SHA1 , db , p , plen ) ;
/* Step 3:
* Generate an octet string PS consisting of emLen - | | M | | - 2 hLen - 1 zero
* octets . The length of PS may be 0.
*/
memset ( db + hlen , 0 , pslen ) ;
/* Step 5:
* Concatenate pHash , PS , the message M , and other padding to form a data
* block DB as : DB = pHash | | PS | | 01 | | M
*/
db [ hlen + pslen ] = 0x01 ;
memcpy ( db + hlen + pslen + 1 , m , mlen ) ;
/* Step 6:
* Generate a random octet string seed of length hLen
*/
seed = gcry_random_bytes ( hlen , GCRY_STRONG_RANDOM ) ;
if ( ! seed )
{
DPRINTF ( E_LOG , L_RAOP , " Could not allocate memory for OAEP seed \n " ) ;
ret = - 1 ;
goto out_free_alloced ;
}
/* Step 7:
* Let dbMask = MGF ( seed , emLen - hLen ) .
*/
ret = raop_crypt_mgf1 ( db_mask , emlen_max - hlen , seed , hlen , GCRY_MD_SHA1 ) ;
if ( ret < 0 )
goto out_free_all ;
/* Step 8:
* Let maskedDB = DB \ xor dbMask .
*/
for ( i = 0 ; i < ( emlen_max - hlen ) ; i + + )
db [ i ] ^ = db_mask [ i ] ;
/* Step 9:
* Let seedMask = MGF ( maskedDB , hLen ) .
*/
ret = raop_crypt_mgf1 ( seed_mask , hlen , db , emlen_max - hlen , GCRY_MD_SHA1 ) ;
if ( ret < 0 )
goto out_free_all ;
/* Step 10:
* Let maskedSeed = seed \ xor seedMask .
*/
for ( i = 0 ; i < hlen ; i + + )
seed [ i ] ^ = seed_mask [ i ] ;
/* Step 11:
* Let EM = maskedSeed | | maskedDB .
*/
em [ 0 ] = 0x00 ;
memcpy ( em + 1 , seed , hlen ) ;
memcpy ( em + 1 + hlen , db , hlen + pslen + 1 + mlen ) ;
/* Step 12:
* Output EM .
*/
ret = 0 ;
out_free_all :
free ( seed ) ;
out_free_alloced :
free ( db ) ;
free ( db_mask ) ;
free ( seed_mask ) ;
return ret ;
}
static char *
raop_crypt_encrypt_aes_key_base64 ( void )
{
char ebuf [ 64 ] ;
uint8_t padded_key [ 256 ] ;
gpg_error_t gc_err ;
gcry_sexp_t sexp_rsa_params ;
gcry_sexp_t sexp_input ;
gcry_sexp_t sexp_encrypted ;
gcry_sexp_t sexp_token_a ;
gcry_mpi_t mpi_pubkey ;
gcry_mpi_t mpi_exp ;
gcry_mpi_t mpi_input ;
gcry_mpi_t mpi_output ;
char * result ;
uint8_t * value ;
size_t value_size ;
int ret ;
result = NULL ;
/* Add RSA-OAES-SHA1 padding */
ret = raop_crypt_add_oaep_padding ( padded_key , sizeof ( padded_key ) , raop_aes_key , sizeof ( raop_aes_key ) , NULL , 0 ) ;
if ( ret < 0 )
return NULL ;
/* Read public key */
gc_err = gcry_mpi_scan ( & mpi_pubkey , GCRYMPI_FMT_USG , raop_rsa_pubkey , sizeof ( raop_rsa_pubkey ) - 1 , NULL ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not read RAOP RSA pubkey: %s \n " , ebuf ) ;
return NULL ;
}
/* Read exponent */
gc_err = gcry_mpi_scan ( & mpi_exp , GCRYMPI_FMT_USG , raop_rsa_exp , sizeof ( raop_rsa_exp ) - 1 , NULL ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not read RAOP RSA exponent: %s \n " , ebuf ) ;
goto out_free_mpi_pubkey ;
}
/* If the input data starts with a set bit (0x80), gcrypt thinks it's a
* signed integer and complains . Prefixing it with a zero byte ( \ 0 )
* works , but involves more work . Converting it to an MPI in our code is
* cleaner .
*/
gc_err = gcry_mpi_scan ( & mpi_input , GCRYMPI_FMT_USG , padded_key , sizeof ( padded_key ) , NULL ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not convert input data: %s \n " , ebuf ) ;
goto out_free_mpi_exp ;
}
/* Build S-expression with RSA parameters */
gc_err = gcry_sexp_build ( & sexp_rsa_params , NULL , " (public-key(rsa(n %m)(e %m))) " , mpi_pubkey , mpi_exp ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not build RSA params S-exp: %s \n " , ebuf ) ;
goto out_free_mpi_input ;
}
/* Build S-expression for data */
gc_err = gcry_sexp_build ( & sexp_input , NULL , " (data(value %m)) " , mpi_input ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not build data S-exp: %s \n " , ebuf ) ;
goto out_free_sexp_params ;
}
/* Encrypt data */
gc_err = gcry_pk_encrypt ( & sexp_encrypted , sexp_input , sexp_rsa_params ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not encrypt data: %s \n " , ebuf ) ;
goto out_free_sexp_input ;
}
/* Extract encrypted data */
sexp_token_a = gcry_sexp_find_token ( sexp_encrypted , " a " , 0 ) ;
if ( ! sexp_token_a )
{
DPRINTF ( E_LOG , L_RAOP , " Could not find token 'a' in result S-exp \n " ) ;
goto out_free_sexp_encrypted ;
}
mpi_output = gcry_sexp_nth_mpi ( sexp_token_a , 1 , GCRYMPI_FMT_USG ) ;
if ( ! mpi_output )
{
DPRINTF ( E_LOG , L_RAOP , " Cannot extract MPI from result \n " ) ;
goto out_free_sexp_token_a ;
}
/* Copy encrypted data into char array */
gc_err = gcry_mpi_aprint ( GCRYMPI_FMT_USG , & value , & value_size , mpi_output ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not copy encrypted data: %s \n " , ebuf ) ;
goto out_free_mpi_output ;
}
/* Encode in Base64 */
result = b64_encode ( value , value_size ) ;
free ( value ) ;
out_free_mpi_output :
gcry_mpi_release ( mpi_output ) ;
out_free_sexp_token_a :
gcry_sexp_release ( sexp_token_a ) ;
out_free_sexp_encrypted :
gcry_sexp_release ( sexp_encrypted ) ;
out_free_sexp_input :
gcry_sexp_release ( sexp_input ) ;
out_free_sexp_params :
gcry_sexp_release ( sexp_rsa_params ) ;
out_free_mpi_input :
gcry_mpi_release ( mpi_input ) ;
out_free_mpi_exp :
gcry_mpi_release ( mpi_exp ) ;
out_free_mpi_pubkey :
gcry_mpi_release ( mpi_pubkey ) ;
return result ;
}
2011-03-08 16:04:48 -05:00
/* RAOP metadata */
2015-04-11 14:53:09 -04:00
static void
2011-03-08 16:04:48 -05:00
raop_metadata_free ( struct raop_metadata * rmd )
{
evbuffer_free ( rmd - > metadata ) ;
if ( rmd - > artwork )
evbuffer_free ( rmd - > artwork ) ;
free ( rmd ) ;
}
2016-01-23 19:14:07 -05:00
static void
2011-03-08 16:04:48 -05:00
raop_metadata_purge ( void )
{
struct raop_metadata * rmd ;
for ( rmd = metadata_head ; rmd ; rmd = metadata_head )
{
metadata_head = rmd - > next ;
raop_metadata_free ( rmd ) ;
}
metadata_tail = NULL ;
}
2016-01-23 19:14:07 -05:00
static void
2011-03-08 16:04:48 -05:00
raop_metadata_prune ( uint64_t rtptime )
{
struct raop_metadata * rmd ;
for ( rmd = metadata_head ; rmd ; rmd = metadata_head )
{
if ( rmd - > end > = rtptime )
break ;
if ( metadata_tail = = metadata_head )
metadata_tail = rmd - > next ;
metadata_head = rmd - > next ;
raop_metadata_free ( rmd ) ;
}
}
2015-04-07 17:35:56 -04:00
/* Thread: worker */
2016-01-23 19:14:07 -05:00
static void *
2015-04-09 15:04:35 -04:00
raop_metadata_prepare ( int id )
2011-03-08 16:04:48 -05:00
{
2016-12-03 14:40:54 -05:00
struct db_queue_item * queue_item ;
2011-03-08 16:04:48 -05:00
struct raop_metadata * rmd ;
struct evbuffer * tmp ;
int ret ;
rmd = ( struct raop_metadata * ) malloc ( sizeof ( struct raop_metadata ) ) ;
if ( ! rmd )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for RAOP metadata \n " ) ;
return NULL ;
}
memset ( rmd , 0 , sizeof ( struct raop_metadata ) ) ;
2016-12-03 14:40:54 -05:00
queue_item = db_queue_fetch_byitemid ( id ) ;
if ( ! queue_item )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for queue item \n " ) ;
goto out_rmd ;
}
2015-03-31 17:40:52 -04:00
/* Get artwork */
2015-03-30 17:11:21 -04:00
rmd - > artwork = evbuffer_new ( ) ;
if ( ! rmd - > artwork )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for artwork evbuffer; no artwork will be sent \n " ) ;
goto skip_artwork ;
}
2016-12-03 14:40:54 -05:00
ret = artwork_get_item ( rmd - > artwork , queue_item - > file_id , 600 , 600 ) ;
2015-03-30 17:11:21 -04:00
if ( ret < 0 )
{
2015-04-09 15:04:35 -04:00
DPRINTF ( E_INFO , L_RAOP , " Failed to retrieve artwork for file id %d; no artwork will be sent \n " , id ) ;
2015-03-30 17:11:21 -04:00
evbuffer_free ( rmd - > artwork ) ;
rmd - > artwork = NULL ;
}
rmd - > artwork_fmt = ret ;
skip_artwork :
2011-03-08 16:04:48 -05:00
/* Turn it into DAAP metadata */
tmp = evbuffer_new ( ) ;
if ( ! tmp )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for temporary metadata evbuffer; metadata will not be sent \n " ) ;
2016-12-03 14:40:54 -05:00
goto out_qi ;
2011-03-08 16:04:48 -05:00
}
rmd - > metadata = evbuffer_new ( ) ;
if ( ! rmd - > metadata )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for metadata evbuffer; metadata will not be sent \n " ) ;
evbuffer_free ( tmp ) ;
2016-12-03 14:40:54 -05:00
goto out_qi ;
2011-03-08 16:04:48 -05:00
}
2016-12-03 14:40:54 -05:00
ret = dmap_encode_queue_metadata ( rmd - > metadata , tmp , queue_item ) ;
2011-03-08 16:04:48 -05:00
evbuffer_free ( tmp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not encode file metadata; metadata will not be sent \n " ) ;
goto out_metadata ;
}
2016-12-03 14:40:54 -05:00
/* Progress - raop_metadata_send() will add rtptime to these */
2015-04-09 15:04:35 -04:00
rmd - > start = 0 ;
2017-01-23 17:41:10 -05:00
rmd - > end = ( ( uint64_t ) queue_item - > song_length * 44100UL ) / 1000UL ;
2016-12-03 14:40:54 -05:00
free_queue_item ( queue_item , 0 ) ;
2011-03-08 16:04:48 -05:00
return rmd ;
out_metadata :
evbuffer_free ( rmd - > metadata ) ;
2016-12-03 14:40:54 -05:00
out_qi :
free_queue_item ( queue_item , 0 ) ;
2011-03-08 16:04:48 -05:00
out_rmd :
free ( rmd ) ;
return NULL ;
}
2010-05-02 05:32:16 -04:00
/* Helpers */
static int
2010-09-25 14:15:27 -04:00
raop_add_auth ( struct raop_session * rs , struct evrtsp_request * req , const char * method , const char * uri )
2010-05-02 05:32:16 -04:00
{
char ha1 [ 33 ] ;
char ha2 [ 33 ] ;
char ebuf [ 64 ] ;
char auth [ 256 ] ;
2010-09-21 16:29:17 -04:00
const char * hash_fmt ;
const char * username ;
2010-05-02 05:32:16 -04:00
uint8_t * hash_bytes ;
size_t hashlen ;
gcry_md_hd_t hd ;
gpg_error_t gc_err ;
int i ;
int ret ;
rs - > req_has_auth = 0 ;
if ( ! rs - > nonce )
return 0 ;
if ( ! rs - > password )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Authentication required but no password found for device '%s' \n " , rs - > devname ) ;
2010-05-02 05:32:16 -04:00
return - 2 ;
}
2010-09-21 16:29:17 -04:00
if ( rs - > auth_quirk_itunes )
{
hash_fmt = " %02X " ; /* Uppercase hex */
username = " iTunes " ;
}
else
{
hash_fmt = " %02x " ;
username = " " ; /* No username */
}
2010-05-02 05:32:16 -04:00
gc_err = gcry_md_open ( & hd , GCRY_MD_MD5 , 0 ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not open MD5: %s \n " , ebuf ) ;
return - 1 ;
}
memset ( ha1 , 0 , sizeof ( ha1 ) ) ;
memset ( ha2 , 0 , sizeof ( ha2 ) ) ;
hashlen = gcry_md_get_algo_dlen ( GCRY_MD_MD5 ) ;
/* HA 1 */
2010-09-21 16:29:17 -04:00
gcry_md_write ( hd , username , strlen ( username ) ) ;
2010-05-02 05:32:16 -04:00
gcry_md_write ( hd , " : " , 1 ) ;
gcry_md_write ( hd , rs - > realm , strlen ( rs - > realm ) ) ;
gcry_md_write ( hd , " : " , 1 ) ;
gcry_md_write ( hd , rs - > password , strlen ( rs - > password ) ) ;
hash_bytes = gcry_md_read ( hd , GCRY_MD_MD5 ) ;
if ( ! hash_bytes )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read MD5 hash \n " ) ;
return - 1 ;
}
for ( i = 0 ; i < hashlen ; i + + )
2010-09-21 16:29:17 -04:00
sprintf ( ha1 + ( 2 * i ) , hash_fmt , hash_bytes [ i ] ) ;
2010-05-02 05:32:16 -04:00
/* RESET */
gcry_md_reset ( hd ) ;
/* HA 2 */
gcry_md_write ( hd , method , strlen ( method ) ) ;
gcry_md_write ( hd , " : " , 1 ) ;
gcry_md_write ( hd , uri , strlen ( uri ) ) ;
hash_bytes = gcry_md_read ( hd , GCRY_MD_MD5 ) ;
if ( ! hash_bytes )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read MD5 hash \n " ) ;
return - 1 ;
}
for ( i = 0 ; i < hashlen ; i + + )
2010-09-21 16:29:17 -04:00
sprintf ( ha2 + ( 2 * i ) , hash_fmt , hash_bytes [ i ] ) ;
2010-05-02 05:32:16 -04:00
/* RESET */
gcry_md_reset ( hd ) ;
/* Final value */
gcry_md_write ( hd , ha1 , 32 ) ;
gcry_md_write ( hd , " : " , 1 ) ;
gcry_md_write ( hd , rs - > nonce , strlen ( rs - > nonce ) ) ;
gcry_md_write ( hd , " : " , 1 ) ;
gcry_md_write ( hd , ha2 , 32 ) ;
hash_bytes = gcry_md_read ( hd , GCRY_MD_MD5 ) ;
if ( ! hash_bytes )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read MD5 hash \n " ) ;
return - 1 ;
}
for ( i = 0 ; i < hashlen ; i + + )
2010-09-21 16:29:17 -04:00
sprintf ( ha1 + ( 2 * i ) , hash_fmt , hash_bytes [ i ] ) ;
2010-05-02 05:32:16 -04:00
gcry_md_close ( hd ) ;
/* Build header */
2010-09-21 16:29:17 -04:00
ret = snprintf ( auth , sizeof ( auth ) , " Digest username= \" %s \" , realm= \" %s \" , nonce= \" %s \" , uri= \" %s \" , response= \" %s \" " ,
username , rs - > realm , rs - > nonce , uri , ha1 ) ;
2010-05-02 05:32:16 -04:00
if ( ( ret < 0 ) | | ( ret > = sizeof ( auth ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " Authorization value header exceeds buffer size \n " ) ;
return - 1 ;
}
evrtsp_add_header ( req - > output_headers , " Authorization " , auth ) ;
DPRINTF ( E_DBG , L_RAOP , " Authorization header: %s \n " , auth ) ;
rs - > req_has_auth = 1 ;
return 0 ;
}
static int
raop_parse_auth ( struct raop_session * rs , struct evrtsp_request * req )
{
const char * param ;
char * auth ;
char * token ;
char * ptr ;
if ( rs - > realm )
{
free ( rs - > realm ) ;
rs - > realm = NULL ;
}
if ( rs - > nonce )
{
free ( rs - > nonce ) ;
rs - > nonce = NULL ;
}
param = evrtsp_find_header ( req - > input_headers , " WWW-Authenticate " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_RAOP , " WWW-Authenticate header not found \n " ) ;
return - 1 ;
}
2010-09-25 13:00:03 -04:00
DPRINTF ( E_DBG , L_RAOP , " WWW-Authenticate: %s \n " , param ) ;
2010-05-02 05:32:16 -04:00
if ( strncmp ( param , " Digest " , strlen ( " Digest " ) ) ! = 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Unsupported authentication method: %s \n " , param ) ;
return - 1 ;
}
auth = strdup ( param ) ;
if ( ! auth )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for WWW-Authenticate header copy \n " ) ;
return - 1 ;
}
token = strchr ( auth , ' ' ) ;
token + + ;
token = strtok_r ( token , " = " , & ptr ) ;
while ( token )
{
if ( strcmp ( token , " realm " ) = = 0 )
{
2010-09-25 13:00:03 -04:00
token = strtok_r ( NULL , " = \" " , & ptr ) ;
2010-05-02 05:32:16 -04:00
if ( ! token )
break ;
2010-09-25 13:00:03 -04:00
rs - > realm = strdup ( token ) ;
2010-05-02 05:32:16 -04:00
}
else if ( strcmp ( token , " nonce " ) = = 0 )
{
2010-09-25 13:00:03 -04:00
token = strtok_r ( NULL , " = \" " , & ptr ) ;
2010-05-02 05:32:16 -04:00
if ( ! token )
break ;
2010-09-25 13:00:03 -04:00
rs - > nonce = strdup ( token ) ;
2010-05-02 05:32:16 -04:00
}
token = strtok_r ( NULL , " = " , & ptr ) ;
}
free ( auth ) ;
if ( ! rs - > realm | | ! rs - > nonce )
{
DPRINTF ( E_LOG , L_RAOP , " Could not find realm/nonce in WWW-Authenticate header \n " ) ;
if ( rs - > realm )
{
free ( rs - > realm ) ;
rs - > realm = NULL ;
}
if ( rs - > nonce )
{
free ( rs - > nonce ) ;
rs - > nonce = NULL ;
}
return - 1 ;
}
2010-09-25 13:00:03 -04:00
DPRINTF ( E_DBG , L_RAOP , " Found realm: [%s], nonce: [%s] \n " , rs - > realm , rs - > nonce ) ;
2010-05-02 05:32:16 -04:00
return 0 ;
}
2010-09-25 14:15:27 -04:00
static int
raop_add_headers ( struct raop_session * rs , struct evrtsp_request * req , enum evrtsp_cmd_type req_method )
2010-09-25 13:49:31 -04:00
{
char buf [ 64 ] ;
2010-09-25 14:15:27 -04:00
const char * method ;
const char * url ;
int ret ;
2010-09-25 13:49:31 -04:00
2010-12-12 09:06:57 -05:00
method = evrtsp_method ( req_method ) ;
2016-10-12 16:24:58 -04:00
DPRINTF ( E_DBG , L_RAOP , " Building %s for '%s' \n " , method , rs - > devname ) ;
2010-12-12 09:06:57 -05:00
2010-09-25 13:49:31 -04:00
snprintf ( buf , sizeof ( buf ) , " %d " , rs - > cseq ) ;
evrtsp_add_header ( req - > output_headers , " CSeq " , buf ) ;
2011-03-11 12:53:38 -05:00
rs - > cseq + + ;
2010-09-25 13:49:31 -04:00
evrtsp_add_header ( req - > output_headers , " User-Agent " , " forked-daapd/ " VERSION ) ;
2010-09-25 14:15:27 -04:00
/* Add Authorization header */
url = ( req_method = = EVRTSP_REQ_OPTIONS ) ? " * " : rs - > session_url ;
ret = raop_add_auth ( rs , req , method , url ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not add Authorization header \n " ) ;
if ( ret = = - 2 )
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_PASSWORD ;
2010-09-25 14:15:27 -04:00
return - 1 ;
}
2010-09-25 13:49:31 -04:00
snprintf ( buf , sizeof ( buf ) , " % " PRIX64 , libhash ) ;
evrtsp_add_header ( req - > output_headers , " Client-Instance " , buf ) ;
evrtsp_add_header ( req - > output_headers , " DACP-ID " , buf ) ;
if ( rs - > session )
evrtsp_add_header ( req - > output_headers , " Session " , rs - > session ) ;
/* Content-Length added automatically by evrtsp */
2010-09-25 14:15:27 -04:00
return 0 ;
2010-09-25 13:49:31 -04:00
}
2015-02-09 11:55:12 -05:00
/* This check should compare the reply CSeq with the request CSeq, but it has
* been removed because RAOP targets like Reflector and AirFoil don ' t return
* the CSeq according to the rtsp spec , and the CSeq is not really important
* anyway .
*/
2011-03-11 12:32:46 -05:00
static int
raop_check_cseq ( struct raop_session * rs , struct evrtsp_request * req )
{
2015-02-09 11:55:12 -05:00
return 0 ;
2010-05-02 05:32:16 -04:00
}
static int
2017-05-04 13:31:26 -04:00
raop_make_sdp ( struct raop_session * rs , struct evrtsp_request * req , char * address , int family , uint32_t session_id )
2010-05-02 05:32:16 -04:00
{
# define SDP_PLD_FMT \
" v=0 \r \n " \
2017-05-04 13:31:26 -04:00
" o=iTunes %u 0 IN %s %s \r \n " \
2010-05-02 05:32:16 -04:00
" s=iTunes \r \n " \
2017-05-04 13:31:26 -04:00
" c=IN %s %s \r \n " \
2010-05-02 05:32:16 -04:00
" t=0 0 \r \n " \
" m=audio 0 RTP/AVP 96 \r \n " \
" a=rtpmap:96 AppleLossless \r \n " \
" a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100 \r \n " \
" a=rsaaeskey:%s \r \n " \
" a=aesiv:%s \r \n "
2013-10-13 15:48:15 -04:00
# define SDP_PLD_FMT_NO_ENC \
" v=0 \r \n " \
2017-05-04 13:31:26 -04:00
" o=iTunes %u 0 IN %s %s \r \n " \
2013-10-13 15:48:15 -04:00
" s=iTunes \r \n " \
2017-05-04 13:31:26 -04:00
" c=IN %s %s \r \n " \
2013-10-13 15:48:15 -04:00
" t=0 0 \r \n " \
" m=audio 0 RTP/AVP 96 \r \n " \
" a=rtpmap:96 AppleLossless \r \n " \
" a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100 \r \n "
2010-05-02 05:32:16 -04:00
2017-05-04 13:31:26 -04:00
const char * af ;
const char * rs_af ;
2010-10-02 06:41:45 -04:00
char * p ;
2010-05-02 05:32:16 -04:00
int ret ;
2017-05-04 13:31:26 -04:00
af = ( family = = AF_INET ) ? " IP4 " : " IP6 " ;
rs_af = ( rs - > family = = AF_INET ) ? " IP4 " : " IP6 " ;
2010-10-02 06:41:45 -04:00
p = strchr ( rs - > address , ' % ' ) ;
if ( p )
* p = ' \0 ' ;
2013-10-13 15:48:15 -04:00
/* Add SDP payload - but don't add RSA/AES key/iv if no encryption - important for ATV3 update 6.0 */
if ( rs - > encrypt )
ret = evbuffer_add_printf ( req - > output_buffer , SDP_PLD_FMT ,
2017-05-04 13:31:26 -04:00
session_id , af , address , rs_af , rs - > address , AIRTUNES_V2_PACKET_SAMPLES ,
2013-10-13 15:48:15 -04:00
raop_aes_key_b64 , raop_aes_iv_b64 ) ;
else
ret = evbuffer_add_printf ( req - > output_buffer , SDP_PLD_FMT_NO_ENC ,
2017-05-04 13:31:26 -04:00
session_id , af , address , rs_af , rs - > address , AIRTUNES_V2_PACKET_SAMPLES ) ;
2010-05-02 05:32:16 -04:00
2010-10-02 06:41:45 -04:00
if ( p )
* p = ' % ' ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for SDP payload \n " ) ;
return - 1 ;
}
2017-03-04 17:05:59 -05:00
DPRINTF ( E_INFO , L_RAOP , " Setting up AirPlay session %u (%s -> %s) \n " , session_id , address , rs - > address ) ;
2010-05-02 05:32:16 -04:00
return 0 ;
# undef SDP_PLD_FMT
2013-10-13 15:48:15 -04:00
# undef SDP_PLD_FMT_NO_ENC
2010-05-02 05:32:16 -04:00
}
/* RAOP/RTSP requests */
2011-03-26 12:39:01 -04:00
/*
* Request queueing HOWTO
*
* Sending :
* - increment rs - > reqs_in_flight
* - set evrtsp connection closecb to NULL
*
* Request callback :
* - decrement rs - > reqs_in_flight first thing , even if the callback is
* called for error handling ( req = = NULL or HTTP error code )
* - if rs - > reqs_in_flight = = 0 , setup evrtsp connection closecb
*
* When a request fails , the whole RAOP session is declared failed and
* torn down by calling raop_session_failure ( ) , even if there are requests
* queued on the evrtsp connection . There is no reason to think pending
* requests would work out better than the one that just failed and recovery
* would be tricky to get right .
*
* evrtsp behaviour with queued requests :
* - request callback is called with req = = NULL to indicate a connection
* error ; if there are several requests queued on the connection , this can
* happen for each request if the connection isn ' t destroyed
* - the connection is reset , and the closecb is called if the connection was
* previously connected . There is no closecb set when there are requests in
* flight
*/
2010-05-02 05:32:16 -04:00
static int
raop_send_req_teardown ( struct raop_session * rs , evrtsp_req_cb cb )
{
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for TEARDOWN \n " ) ;
return - 1 ;
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_TEARDOWN ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2016-04-04 04:06:58 -04:00
rs - > state = RAOP_STATE_CONNECTED ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
return 0 ;
}
static int
2011-03-08 13:00:14 -05:00
raop_send_req_flush ( struct raop_session * rs , uint64_t rtptime , evrtsp_req_cb cb )
2010-05-02 05:32:16 -04:00
{
char buf [ 64 ] ;
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for FLUSH \n " ) ;
return - 1 ;
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_FLUSH ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
/* Restart sequence: last sequence + 1 */
2017-10-05 16:13:01 -04:00
ret = snprintf ( buf , sizeof ( buf ) , " seq=% " PRIu16 " ;rtptime=%u " , stream_seq + 1 , RAOP_RTPTIME ( rtptime ) ) ;
2010-05-02 05:32:16 -04:00
if ( ( ret < 0 ) | | ( ret > = sizeof ( buf ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " RTP-Info too big for buffer in FLUSH request \n " ) ;
2010-05-08 06:16:54 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
evrtsp_add_header ( req - > output_headers , " RTP-Info " , buf ) ;
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
return 0 ;
}
static int
2011-03-08 13:07:26 -05:00
raop_send_req_set_parameter ( struct raop_session * rs , struct evbuffer * evbuf , char * ctype , char * rtpinfo , evrtsp_req_cb cb )
2010-05-02 05:32:16 -04:00
{
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for SET_PARAMETER \n " ) ;
return - 1 ;
}
ret = evbuffer_add_buffer ( req - > output_buffer , evbuf ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for SET_PARAMETER payload \n " ) ;
2010-05-08 06:16:54 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_SET_PARAMETER ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2011-03-08 13:01:44 -05:00
evrtsp_add_header ( req - > output_headers , " Content-Type " , ctype ) ;
2010-05-02 05:32:16 -04:00
2011-03-08 13:07:26 -05:00
if ( rtpinfo )
evrtsp_add_header ( req - > output_headers , " RTP-Info " , rtpinfo ) ;
2010-05-02 05:32:16 -04:00
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
return 0 ;
}
static int
raop_send_req_record ( struct raop_session * rs , evrtsp_req_cb cb )
{
char buf [ 64 ] ;
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for RECORD \n " ) ;
return - 1 ;
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_RECORD ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
evrtsp_add_header ( req - > output_headers , " Range " , " npt=0- " ) ;
/* Start sequence: next sequence */
2017-10-05 16:13:01 -04:00
ret = snprintf ( buf , sizeof ( buf ) , " seq=% " PRIu16 " ;rtptime=%u " , stream_seq + 1 , RAOP_RTPTIME ( rs - > start_rtptime ) ) ;
2010-05-02 05:32:16 -04:00
if ( ( ret < 0 ) | | ( ret > = sizeof ( buf ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " RTP-Info too big for buffer in RECORD request \n " ) ;
2010-05-08 06:16:54 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
evrtsp_add_header ( req - > output_headers , " RTP-Info " , buf ) ;
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
return 0 ;
}
static int
raop_send_req_setup ( struct raop_session * rs , evrtsp_req_cb cb )
{
char hdr [ 128 ] ;
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for SETUP \n " ) ;
return - 1 ;
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_SETUP ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
/* Request UDP transport, AirTunes v2 streaming */
2010-05-14 11:38:05 -04:00
ret = snprintf ( hdr , sizeof ( hdr ) , " RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%u;timing_port=%u " ,
rs - > control_svc - > port , rs - > timing_svc - > port ) ;
2010-05-02 05:32:16 -04:00
if ( ( ret < 0 ) | | ( ret > = sizeof ( hdr ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " Transport header exceeds buffer length \n " ) ;
2010-05-08 06:16:54 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
evrtsp_add_header ( req - > output_headers , " Transport " , hdr ) ;
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
return 0 ;
}
static int
raop_send_req_announce ( struct raop_session * rs , evrtsp_req_cb cb )
{
uint8_t challenge [ 16 ] ;
char * challenge_b64 ;
char * ptr ;
struct evrtsp_request * req ;
char * address ;
2010-07-10 06:27:39 -04:00
char * intf ;
2010-05-02 05:32:16 -04:00
unsigned short port ;
2017-05-04 13:31:26 -04:00
int family ;
2010-05-02 05:32:16 -04:00
uint32_t session_id ;
int ret ;
/* Determine local address, needed for SDP and session URL */
2017-05-04 13:31:26 -04:00
evrtsp_connection_get_local_address ( rs - > ctrl , & address , & port , & family ) ;
2010-05-02 05:32:16 -04:00
if ( ! address | | ( port = = 0 ) )
{
DPRINTF ( E_LOG , L_RAOP , " Could not determine local address \n " ) ;
if ( address )
free ( address ) ;
return - 1 ;
}
2010-07-10 06:27:39 -04:00
intf = strchr ( address , ' % ' ) ;
if ( intf )
{
* intf = ' \0 ' ;
intf + + ;
}
DPRINTF ( E_DBG , L_RAOP , " Local address: %s (LL: %s) port %d \n " , address , ( intf ) ? intf : " no " , port ) ;
2010-05-02 05:32:16 -04:00
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for ANNOUNCE \n " ) ;
free ( address ) ;
return - 1 ;
}
/* Session ID and session URL */
gcry_randomize ( & session_id , sizeof ( session_id ) , GCRY_STRONG_RANDOM ) ;
ret = snprintf ( rs - > session_url , sizeof ( rs - > session_url ) , " rtsp://%s/%u " , address , session_id ) ;
if ( ( ret < 0 ) | | ( ret > = sizeof ( rs - > session_url ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " Session URL length exceeds 127 characters \n " ) ;
free ( address ) ;
2010-05-08 06:16:54 -04:00
goto cleanup_req ;
2010-05-02 05:32:16 -04:00
}
/* SDP payload */
2017-05-04 13:31:26 -04:00
ret = raop_make_sdp ( rs , req , address , family , session_id ) ;
2010-05-02 05:32:16 -04:00
free ( address ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not generate SDP payload for ANNOUNCE \n " ) ;
2010-05-08 06:16:54 -04:00
goto cleanup_req ;
2010-05-02 05:32:16 -04:00
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_ANNOUNCE ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
evrtsp_add_header ( req - > output_headers , " Content-Type " , " application/sdp " ) ;
2013-10-13 15:48:15 -04:00
/* Challenge - but only if session is encrypted (important for ATV3 after update 6.0) */
if ( rs - > encrypt )
2010-05-02 05:32:16 -04:00
{
2013-10-13 15:48:15 -04:00
gcry_randomize ( challenge , sizeof ( challenge ) , GCRY_STRONG_RANDOM ) ;
challenge_b64 = b64_encode ( challenge , sizeof ( challenge ) ) ;
if ( ! challenge_b64 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't encode challenge \n " ) ;
2010-05-02 05:32:16 -04:00
2013-10-13 15:48:15 -04:00
goto cleanup_req ;
}
2010-05-02 05:32:16 -04:00
2013-10-13 15:48:15 -04:00
/* Remove base64 padding */
ptr = strchr ( challenge_b64 , ' = ' ) ;
if ( ptr )
* ptr = ' \0 ' ;
2010-05-02 05:32:16 -04:00
2013-10-13 15:48:15 -04:00
evrtsp_add_header ( req - > output_headers , " Apple-Challenge " , challenge_b64 ) ;
2010-05-02 05:32:16 -04:00
2013-10-13 15:48:15 -04:00
free ( challenge_b64 ) ;
}
2010-05-02 05:32:16 -04:00
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 " ) ;
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
return 0 ;
2010-05-08 06:16:54 -04:00
cleanup_req :
2010-05-02 05:32:16 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
}
static int
raop_send_req_options ( struct raop_session * rs , evrtsp_req_cb cb )
{
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for OPTIONS \n " ) ;
return - 1 ;
}
2010-09-25 14:15:27 -04:00
ret = raop_add_headers ( rs , req , EVRTSP_REQ_OPTIONS ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
2010-05-08 06:16:54 -04:00
evrtsp_request_free ( req ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
ret = evrtsp_make_request ( rs - > ctrl , req , EVRTSP_REQ_OPTIONS , " * " ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not make OPTIONS request \n " ) ;
2017-06-19 15:52:01 -04:00
return - 1 ;
}
rs - > reqs_in_flight + + ;
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
return 0 ;
}
# ifdef RAOP_VERIFICATION
static int
raop_send_req_pin_start ( struct raop_session * rs , evrtsp_req_cb cb )
{
struct evrtsp_request * req ;
int ret ;
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for pair-pin-start \n " ) ;
return - 1 ;
}
ret = raop_add_headers ( rs , req , EVRTSP_REQ_POST ) ;
if ( ret < 0 )
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
DPRINTF ( E_LOG , L_RAOP , " Starting device verification for '%s', please submit PIN via a *.verification file \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 " ) ;
2010-05-02 05:32:16 -04:00
2010-05-08 06:16:54 -04:00
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
return 0 ;
}
2017-06-19 15:52:01 -04:00
# else
static int
raop_send_req_pin_start ( struct raop_session * rs , evrtsp_req_cb cb )
{
DPRINTF ( E_LOG , L_RAOP , " Device '%s' requires verification, but forked-daapd was built with --disable-verification \n " , rs - > devname ) ;
return - 1 ;
}
# endif
2010-05-02 05:32:16 -04:00
2016-02-12 15:59:26 -05:00
/* Maps our internal state to the generic output state and then makes a callback
* to the player to tell that state
*/
static void
raop_status ( struct raop_session * rs )
{
output_status_cb status_cb = rs - > status_cb ;
enum output_device_state state ;
switch ( rs - > state )
{
2017-06-20 14:00:05 -04:00
case RAOP_STATE_PASSWORD . . . RAOP_STATE_UNVERIFIED :
2016-02-12 15:59:26 -05:00
state = OUTPUT_STATE_PASSWORD ;
break ;
case RAOP_STATE_FAILED :
state = OUTPUT_STATE_FAILED ;
break ;
case RAOP_STATE_STOPPED :
state = OUTPUT_STATE_STOPPED ;
break ;
case RAOP_STATE_STARTUP . . . RAOP_STATE_RECORD :
state = OUTPUT_STATE_STARTUP ;
break ;
case RAOP_STATE_CONNECTED :
state = OUTPUT_STATE_CONNECTED ;
break ;
case RAOP_STATE_STREAMING :
state = OUTPUT_STATE_STREAMING ;
break ;
default :
DPRINTF ( E_LOG , L_RAOP , " Bug! Unhandled state in cast_status() \n " ) ;
state = OUTPUT_STATE_FAILED ;
}
rs - > status_cb = NULL ;
if ( status_cb )
status_cb ( rs - > device , rs - > output_session , state ) ;
2017-11-18 03:54:57 -05:00
if ( rs - > state = = RAOP_STATE_UNVERIFIED )
player_speaker_status_trigger ( ) ;
2016-02-12 15:59:26 -05:00
}
2010-05-02 05:32:16 -04:00
static void
2010-07-30 16:18:06 -04:00
raop_session_free ( struct raop_session * rs )
2010-05-02 05:32:16 -04:00
{
2017-10-05 16:13:01 -04:00
if ( ! rs )
return ;
2010-05-02 05:32:16 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
evrtsp_connection_free ( rs - > ctrl ) ;
2015-10-19 15:15:29 -04:00
event_free ( rs - > deferredev ) ;
2010-05-02 05:32:16 -04:00
close ( rs - > server_fd ) ;
if ( rs - > realm )
free ( rs - > realm ) ;
if ( rs - > nonce )
free ( rs - > nonce ) ;
if ( rs - > session )
free ( rs - > session ) ;
if ( rs - > address )
free ( rs - > address ) ;
if ( rs - > devname )
free ( rs - > devname ) ;
2016-01-23 19:14:07 -05:00
free ( rs - > output_session ) ;
2010-05-02 05:32:16 -04:00
free ( rs ) ;
}
2010-07-30 16:18:06 -04:00
static void
raop_session_cleanup ( struct raop_session * rs )
{
struct raop_session * s ;
2010-10-05 14:02:42 -04:00
struct raop_v2_packet * pkt ;
2013-08-17 17:05:50 -04:00
struct raop_v2_packet * next_pkt ;
2010-07-30 16:18:06 -04:00
if ( rs = = sessions )
sessions = sessions - > next ;
else
{
for ( s = sessions ; s & & ( s - > next ! = rs ) ; s = s - > next )
; /* EMPTY */
if ( ! s )
DPRINTF ( E_WARN , L_RAOP , " WARNING: struct raop_session not found in list; BUG! \n " ) ;
else
s - > next = rs - > next ;
}
raop_session_free ( rs ) ;
2010-10-05 14:02:42 -04:00
/* No more active sessions, free retransmit buffer */
if ( ! sessions )
{
2013-08-17 17:05:50 -04:00
pkt = pktbuf_head ;
while ( pkt )
{
next_pkt = pkt - > next ;
free ( pkt ) ;
pkt = next_pkt ;
}
2010-10-05 14:02:42 -04:00
pktbuf_head = NULL ;
pktbuf_tail = NULL ;
pktbuf_size = 0 ;
}
2010-07-30 16:18:06 -04:00
}
2010-10-02 07:18:12 -04:00
static void
raop_session_failure ( struct raop_session * rs )
{
/* Session failed, let our user know */
2016-02-12 15:59:26 -05:00
if ( rs - > state ! = RAOP_STATE_PASSWORD )
rs - > state = RAOP_STATE_FAILED ;
raop_status ( rs ) ;
2010-10-02 07:18:12 -04:00
raop_session_cleanup ( rs ) ;
}
static void
2015-10-19 15:15:29 -04:00
raop_deferredev_cb ( int fd , short what , void * arg )
2010-10-02 07:18:12 -04:00
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-10-02 07:18:12 -04:00
2016-10-12 16:24:58 -04:00
DPRINTF ( E_DBG , L_RAOP , " Cleaning up failed session (deferred) on device '%s' \n " , rs - > devname ) ;
2010-10-02 07:18:12 -04:00
raop_session_failure ( rs ) ;
}
static void
raop_rtsp_close_cb ( struct evrtsp_connection * evcon , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-10-02 07:18:12 -04:00
struct timeval tv ;
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Device '%s' closed RTSP connection \n " , rs - > devname ) ;
2010-10-02 07:18:12 -04:00
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_FAILED ;
2010-10-02 07:18:12 -04:00
evutil_timerclear ( & tv ) ;
2015-10-19 15:15:29 -04:00
evtimer_add ( rs - > deferredev , & tv ) ;
2010-10-02 07:18:12 -04:00
}
2010-05-02 05:32:16 -04:00
static struct raop_session *
2017-06-19 15:52:01 -04:00
raop_session_make ( struct output_device * rd , int family , output_status_cb cb , bool only_probe )
2010-05-02 05:32:16 -04:00
{
2016-01-23 19:14:07 -05:00
struct output_session * os ;
2010-05-02 05:32:16 -04:00
struct raop_session * rs ;
2016-01-23 19:14:07 -05:00
struct raop_extra * re ;
2010-05-14 11:38:16 -04:00
char * address ;
2010-07-10 06:27:39 -04:00
char * intf ;
2010-05-14 11:38:16 -04:00
unsigned short port ;
2010-05-02 05:32:16 -04:00
int ret ;
2016-01-23 19:14:07 -05:00
re = rd - > extra_device_info ;
2010-05-14 11:38:16 -04:00
switch ( family )
{
case AF_INET :
/* We always have the v4 services, so no need to check */
if ( ! rd - > v4_address )
return NULL ;
address = rd - > v4_address ;
port = rd - > v4_port ;
break ;
case AF_INET6 :
if ( ! rd - > v6_address | | ( timing_6svc . fd < 0 ) | | ( control_6svc . fd < 0 ) )
return NULL ;
address = rd - > v6_address ;
port = rd - > v6_port ;
break ;
default :
return NULL ;
}
2010-05-14 11:37:55 -04:00
2016-01-23 19:14:07 -05:00
os = calloc ( 1 , sizeof ( struct output_session ) ) ;
2016-11-19 17:08:50 -05:00
if ( ! os )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory (os) \n " ) ;
return NULL ;
}
2016-01-23 19:14:07 -05:00
rs = calloc ( 1 , sizeof ( struct raop_session ) ) ;
2016-11-19 17:08:50 -05:00
if ( ! rs )
2010-05-02 05:32:16 -04:00
{
2016-11-19 17:08:50 -05:00
DPRINTF ( E_LOG , L_RAOP , " Out of memory (rs) \n " ) ;
free ( os ) ;
2010-05-02 05:32:16 -04:00
return NULL ;
}
2016-01-23 19:14:07 -05:00
os - > session = rs ;
os - > type = rd - > type ;
2010-05-02 05:32:16 -04:00
2016-01-23 19:14:07 -05:00
rs - > output_session = os ;
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_STOPPED ;
2017-06-19 15:52:01 -04:00
rs - > only_probe = only_probe ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight = 0 ;
2010-05-02 05:32:16 -04:00
rs - > cseq = 1 ;
2016-01-23 19:14:07 -05:00
rs - > device = rd ;
2010-05-02 05:32:16 -04:00
rs - > status_cb = cb ;
rs - > server_fd = - 1 ;
rs - > password = rd - > password ;
2017-06-19 15:52:01 -04:00
2016-01-23 19:14:07 -05:00
rs - > wants_metadata = re - > wants_metadata ;
2010-05-02 05:32:16 -04:00
2016-01-23 19:14:07 -05:00
switch ( re - > devtype )
2010-10-07 13:37:05 -04:00
{
2014-05-18 11:19:50 -04:00
case RAOP_DEV_APEX1_80211G :
2010-10-07 13:37:05 -04:00
rs - > encrypt = 1 ;
rs - > auth_quirk_itunes = 1 ;
break ;
2014-05-18 11:19:50 -04:00
case RAOP_DEV_APEX2_80211N :
2010-10-07 13:37:05 -04:00
rs - > encrypt = 1 ;
rs - > auth_quirk_itunes = 0 ;
break ;
2014-05-18 11:19:50 -04:00
case RAOP_DEV_APEX3_80211N :
2010-10-07 13:37:05 -04:00
rs - > encrypt = 0 ;
rs - > auth_quirk_itunes = 0 ;
break ;
2013-10-13 15:48:15 -04:00
2014-05-18 11:19:50 -04:00
case RAOP_DEV_APPLETV :
2013-06-24 11:13:10 -04:00
rs - > encrypt = 0 ;
rs - > auth_quirk_itunes = 0 ;
2016-10-08 18:42:48 -04:00
break ;
case RAOP_DEV_APPLETV4 :
rs - > encrypt = 0 ;
rs - > auth_quirk_itunes = 0 ;
2013-10-13 15:48:15 -04:00
break ;
2014-05-18 11:19:50 -04:00
case RAOP_DEV_OTHER :
2016-01-23 19:14:07 -05:00
rs - > encrypt = re - > encrypt ;
2014-05-18 11:19:50 -04:00
rs - > auth_quirk_itunes = 0 ;
break ;
2010-10-07 13:37:05 -04:00
}
2015-10-19 15:15:29 -04:00
rs - > deferredev = evtimer_new ( evbase_player , raop_deferredev_cb , rs ) ;
if ( ! rs - > deferredev )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for deferred error handling! \n " ) ;
goto out_free_rs ;
}
2010-05-14 11:38:16 -04:00
rs - > ctrl = evrtsp_connection_new ( address , port ) ;
if ( ! rs - > ctrl )
2010-05-02 05:32:16 -04:00
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Could not create control connection to '%s' (%s) \n " , rd - > name , address ) ;
2010-05-02 05:32:16 -04:00
2015-10-19 15:15:29 -04:00
goto out_free_event ;
2010-05-02 05:32:16 -04:00
}
2010-05-14 11:38:16 -04:00
evrtsp_connection_set_base ( rs - > ctrl , evbase_player ) ;
rs - > sa . ss . ss_family = family ;
switch ( family )
2010-05-02 05:32:16 -04:00
{
2010-05-14 11:38:16 -04:00
case AF_INET :
rs - > timing_svc = & timing_4svc ;
rs - > control_svc = & control_4svc ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:16 -04:00
ret = inet_pton ( AF_INET , address , & rs - > sa . sin . sin_addr ) ;
break ;
case AF_INET6 :
rs - > timing_svc = & timing_6svc ;
rs - > control_svc = & control_6svc ;
2010-07-10 06:27:39 -04:00
intf = strchr ( address , ' % ' ) ;
if ( intf )
* intf = ' \0 ' ;
2010-05-14 11:38:16 -04:00
ret = inet_pton ( AF_INET6 , address , & rs - > sa . sin6 . sin6_addr ) ;
2010-07-10 06:27:39 -04:00
if ( intf )
{
* intf = ' % ' ;
intf + + ;
rs - > sa . sin6 . sin6_scope_id = if_nametoindex ( intf ) ;
if ( rs - > sa . sin6 . sin6_scope_id = = 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not find interface %s \n " , intf ) ;
ret = - 1 ;
break ;
}
}
2010-05-14 11:38:16 -04:00
break ;
default :
ret = - 1 ;
break ;
2010-05-02 05:32:16 -04:00
}
2010-05-14 11:38:16 -04:00
if ( ret < = 0 )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Device '%s' has invalid address (%s) for %s \n " , rd - > name , address , ( family = = AF_INET ) ? " ipv4 " : " ipv6 " ) ;
2010-05-14 11:38:16 -04:00
goto out_free_evcon ;
}
2010-05-02 05:32:16 -04:00
rs - > devname = strdup ( rd - > name ) ;
2010-05-14 11:38:16 -04:00
rs - > address = strdup ( address ) ;
2017-05-04 13:31:26 -04:00
rs - > family = family ;
2010-05-02 05:32:16 -04:00
2010-11-19 15:49:17 -05:00
rs - > volume = rd - > volume ;
2010-05-02 05:32:16 -04:00
rs - > next = sessions ;
sessions = rs ;
return rs ;
2010-05-14 11:38:16 -04:00
out_free_evcon :
evrtsp_connection_free ( rs - > ctrl ) ;
2015-10-19 15:15:29 -04:00
out_free_event :
event_free ( rs - > deferredev ) ;
2010-05-02 05:32:16 -04:00
out_free_rs :
free ( rs ) ;
return NULL ;
}
static void
raop_session_failure_cb ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
raop_session_failure ( rs ) ;
}
2011-03-08 16:04:48 -05:00
/* Metadata handling */
static void
raop_cb_metadata ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2011-03-08 16:04:48 -05:00
int ret ;
rs - > reqs_in_flight - - ;
if ( ! req )
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 ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto error ;
/* No status_cb call, user doesn't want/need to know about the status
* of metadata requests unless they cause the session to fail .
*/
if ( ! rs - > reqs_in_flight )
evrtsp_connection_set_closecb ( rs - > ctrl , raop_rtsp_close_cb , rs ) ;
return ;
error :
raop_session_failure ( rs ) ;
}
static int
raop_metadata_send_progress ( struct raop_session * rs , struct evbuffer * evbuf , struct raop_metadata * rmd , uint64_t offset , uint32_t delay )
{
uint32_t display ;
int ret ;
2011-03-27 12:00:57 -04:00
/* Here's the deal with progress values:
* - first value , called display , is always start minus a delay
* - > delay x1 if streaming is starting for this device ( joining or not )
* - > delay x2 if stream is switching to a new song
* - second value , called start , is the RTP time of the first sample for this
* song for this device
* - > start of song
* - > start of song + offset if device is joining in the middle of a song ,
* or getting out of a pause or seeking
* - third value , called end , is the RTP time of the last sample for this song
*/
2011-03-08 16:04:48 -05:00
display = RAOP_RTPTIME ( rmd - > start - delay ) ;
ret = evbuffer_add_printf ( evbuf , " progress: %u/%u/%u \r \n " , display , RAOP_RTPTIME ( rmd - > start + offset ) , RAOP_RTPTIME ( rmd - > end ) ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not build progress string for sending \n " ) ;
return - 1 ;
}
ret = raop_send_req_set_parameter ( rs , evbuf , " text/parameters " , NULL , raop_cb_metadata ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " Could not send SET_PARAMETER request for metadata \n " ) ;
return ret ;
}
static int
raop_metadata_send_artwork ( struct raop_session * rs , struct evbuffer * evbuf , struct raop_metadata * rmd , char * rtptime )
{
char * ctype ;
2015-10-19 15:15:29 -04:00
uint8_t * buf ;
size_t len ;
2011-03-08 16:04:48 -05:00
int ret ;
switch ( rmd - > artwork_fmt )
{
case ART_FMT_PNG :
ctype = " image/png " ;
break ;
case ART_FMT_JPEG :
ctype = " image/jpeg " ;
break ;
2011-04-30 05:39:12 -04:00
default :
DPRINTF ( E_LOG , L_RAOP , " Unsupported artwork format %d \n " , rmd - > artwork_fmt ) ;
return - 1 ;
}
2015-10-19 15:15:29 -04:00
buf = evbuffer_pullup ( rmd - > artwork , - 1 ) ;
len = evbuffer_get_length ( rmd - > artwork ) ;
ret = evbuffer_add ( evbuf , buf , len ) ;
2011-04-30 05:39:12 -04:00
if ( ret ! = 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not copy artwork for sending \n " ) ;
return - 1 ;
2011-03-08 16:04:48 -05:00
}
ret = raop_send_req_set_parameter ( rs , evbuf , ctype , rtptime , raop_cb_metadata ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " Could not send SET_PARAMETER request for metadata \n " ) ;
return ret ;
}
static int
raop_metadata_send_metadata ( struct raop_session * rs , struct evbuffer * evbuf , struct raop_metadata * rmd , char * rtptime )
{
2015-10-19 15:15:29 -04:00
uint8_t * buf ;
size_t len ;
2011-03-08 16:04:48 -05:00
int ret ;
2015-10-19 15:15:29 -04:00
buf = evbuffer_pullup ( rmd - > metadata , - 1 ) ;
len = evbuffer_get_length ( rmd - > metadata ) ;
ret = evbuffer_add ( evbuf , buf , len ) ;
2011-03-08 16:04:48 -05:00
if ( ret ! = 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not copy metadata for sending \n " ) ;
return - 1 ;
}
ret = raop_send_req_set_parameter ( rs , evbuf , " application/x-dmap-tagged " , rtptime , raop_cb_metadata ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " Could not send SET_PARAMETER request for metadata \n " ) ;
return ret ;
}
static int
raop_metadata_send_internal ( struct raop_session * rs , struct raop_metadata * rmd , uint64_t offset , uint32_t delay )
{
char rtptime [ 32 ] ;
struct evbuffer * evbuf ;
int ret ;
evbuf = evbuffer_new ( ) ;
if ( ! evbuf )
{
DPRINTF ( E_LOG , L_RAOP , " Could not allocate temp evbuffer for metadata processing \n " ) ;
return - 1 ;
}
ret = snprintf ( rtptime , sizeof ( rtptime ) , " rtptime=%u " , RAOP_RTPTIME ( rmd - > start ) ) ;
if ( ( ret < 0 ) | | ( ret > = sizeof ( rtptime ) ) )
{
DPRINTF ( E_LOG , L_RAOP , " RTP-Info too big for buffer while sending metadata \n " ) ;
ret = - 1 ;
goto out ;
}
ret = raop_metadata_send_metadata ( rs , evbuf , rmd , rtptime ) ;
if ( ret < 0 )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Could not send metadata to '%s' \n " , rs - > devname ) ;
2011-03-08 16:04:48 -05:00
ret = - 1 ;
goto out ;
}
if ( ! rmd - > artwork )
goto skip_artwork ;
ret = raop_metadata_send_artwork ( rs , evbuf , rmd , rtptime ) ;
if ( ret < 0 )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Could not send artwork to '%s' \n " , rs - > devname ) ;
2011-03-08 16:04:48 -05:00
ret = - 1 ;
goto out ;
}
skip_artwork :
ret = raop_metadata_send_progress ( rs , evbuf , rmd , offset , delay ) ;
if ( ret < 0 )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Could not send progress to '%s' \n " , rs - > devname ) ;
2011-03-08 16:04:48 -05:00
ret = - 1 ;
goto out ;
}
out :
evbuffer_free ( evbuf ) ;
return ret ;
}
static void
raop_metadata_startup_send ( struct raop_session * rs )
{
struct raop_metadata * rmd ;
uint64_t offset ;
int sent ;
int ret ;
if ( ! rs - > wants_metadata )
return ;
sent = 0 ;
for ( rmd = metadata_head ; rmd ; rmd = rmd - > next )
{
/* Current song */
if ( ( rs - > start_rtptime > = rmd - > start ) & & ( rs - > start_rtptime < rmd - > end ) )
{
offset = rs - > start_rtptime - rmd - > start ;
ret = raop_metadata_send_internal ( rs , rmd , offset , RAOP_MD_DELAY_STARTUP ) ;
if ( ret < 0 )
{
raop_session_failure ( rs ) ;
return ;
}
sent = 1 ;
}
/* Next song(s) */
else if ( sent & & ( rs - > start_rtptime < rmd - > start ) )
{
ret = raop_metadata_send_internal ( rs , rmd , 0 , RAOP_MD_DELAY_SWITCH ) ;
if ( ret < 0 )
{
raop_session_failure ( rs ) ;
return ;
}
}
}
}
2016-01-23 19:14:07 -05:00
static void
raop_metadata_send ( void * metadata , uint64_t rtptime , uint64_t offset , int startup )
2011-03-08 16:04:48 -05:00
{
2016-01-23 19:14:07 -05:00
struct raop_metadata * rmd ;
2011-03-08 16:04:48 -05:00
struct raop_session * rs ;
2016-01-30 19:22:57 -05:00
struct raop_session * next ;
2011-03-08 16:04:48 -05:00
uint32_t delay ;
int ret ;
2016-01-23 19:14:07 -05:00
rmd = metadata ;
2015-04-09 15:04:35 -04:00
rmd - > start + = rtptime ;
rmd - > end + = rtptime ;
2015-04-07 17:35:56 -04:00
/* Add the rmd to the metadata list */
if ( metadata_tail )
metadata_tail - > next = rmd ;
else
{
metadata_head = rmd ;
metadata_tail = rmd ;
}
2011-03-08 16:04:48 -05:00
2016-01-30 19:22:57 -05:00
for ( rs = sessions ; rs ; rs = next )
2011-03-08 16:04:48 -05:00
{
2016-01-30 19:22:57 -05:00
next = rs - > next ;
2016-02-12 15:59:26 -05:00
if ( ! ( rs - > state & RAOP_STATE_F_CONNECTED ) )
2011-03-08 16:04:48 -05:00
continue ;
if ( ! rs - > wants_metadata )
continue ;
2015-04-09 15:04:35 -04:00
delay = ( startup ) ? RAOP_MD_DELAY_STARTUP : RAOP_MD_DELAY_SWITCH ;
2011-03-08 16:04:48 -05:00
2015-04-09 15:04:35 -04:00
ret = raop_metadata_send_internal ( rs , rmd , offset , delay ) ;
2011-03-08 16:04:48 -05:00
if ( ret < 0 )
{
raop_session_failure ( rs ) ;
continue ;
}
}
2015-03-14 16:42:53 -04:00
}
2011-03-08 16:04:48 -05:00
2010-05-02 05:32:16 -04:00
/* Volume handling */
2018-05-06 16:38:41 -04:00
2010-12-04 10:15:49 -05:00
static float
2018-04-11 16:29:07 -04:00
raop_volume_from_pct ( int volume , char * name )
2010-11-19 15:49:17 -05:00
{
2010-12-04 10:15:49 -05:00
float raop_volume ;
2014-02-09 10:07:00 -05:00
cfg_t * airplay ;
int max_volume ;
max_volume = RAOP_CONFIG_MAX_VOLUME ;
airplay = cfg_gettsec ( cfg , " airplay " , name ) ;
if ( airplay )
max_volume = cfg_getint ( airplay , " max_volume " ) ;
if ( ( max_volume < 1 ) | | ( max_volume > RAOP_CONFIG_MAX_VOLUME ) )
{
2018-04-11 16:29:07 -04:00
DPRINTF ( E_LOG , L_RAOP , " Config has bad max_volume (%d) for device '%s', using default instead \n " , max_volume , name ) ;
2014-02-09 10:07:00 -05:00
max_volume = RAOP_CONFIG_MAX_VOLUME ;
}
2010-11-19 15:49:17 -05:00
/* RAOP volume
* - 144.0 is off
2015-09-27 09:24:04 -04:00
* 0 - 100 maps to - 30.0 - 0
2010-11-19 15:49:17 -05:00
*/
2015-09-27 09:24:04 -04:00
if ( volume > 0 & & volume < = 100 )
2014-02-09 10:07:00 -05:00
raop_volume = - 30.0 + ( ( float ) max_volume * ( float ) volume * 30.0 ) / ( 100.0 * RAOP_CONFIG_MAX_VOLUME ) ;
2015-09-27 09:24:04 -04:00
else
raop_volume = - 144.0 ;
2010-11-19 15:49:17 -05:00
return raop_volume ;
}
2010-05-02 05:32:16 -04:00
static int
2010-11-19 15:49:17 -05:00
raop_set_volume_internal ( struct raop_session * rs , int volume , evrtsp_req_cb cb )
2010-05-02 05:32:16 -04:00
{
struct evbuffer * evbuf ;
2010-12-04 10:15:49 -05:00
float raop_volume ;
2010-05-02 05:32:16 -04:00
int ret ;
evbuf = evbuffer_new ( ) ;
if ( ! evbuf )
{
DPRINTF ( E_LOG , L_RAOP , " Could not allocate evbuffer for volume payload \n " ) ;
return - 1 ;
}
2018-04-11 16:29:07 -04:00
raop_volume = raop_volume_from_pct ( volume , rs - > devname ) ;
2010-11-19 15:49:17 -05:00
2010-05-02 05:32:16 -04:00
/* Don't let locales get in the way here */
2015-01-08 16:03:53 -05:00
/* We use -%d and -(int)raop_volume so -0.3 won't become 0.3 */
ret = evbuffer_add_printf ( evbuf , " volume: -%d.%06d \r \n " , - ( int ) raop_volume , - ( int ) ( 1000000.0 * ( raop_volume - ( int ) raop_volume ) ) ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for SET_PARAMETER payload (volume) \n " ) ;
evbuffer_free ( evbuf ) ;
return - 1 ;
}
2011-03-08 13:07:26 -05:00
ret = raop_send_req_set_parameter ( rs , evbuf , " text/parameters " , NULL , cb ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " Could not send SET_PARAMETER request for volume \n " ) ;
evbuffer_free ( evbuf ) ;
2017-06-26 14:11:31 -04:00
rs - > volume = volume ;
2010-05-02 05:32:16 -04:00
return ret ;
}
static void
2011-03-11 12:32:47 -05:00
raop_cb_set_volume ( struct evrtsp_request * req , void * arg )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto error ;
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 ) ;
goto error ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto error ;
/* Let our user know */
2016-02-12 15:59:26 -05:00
raop_status ( rs ) ;
2010-05-02 05:32:16 -04:00
2011-03-11 14:02:29 -05:00
if ( ! rs - > reqs_in_flight )
evrtsp_connection_set_closecb ( rs - > ctrl , raop_rtsp_close_cb , rs ) ;
2010-05-02 05:32:16 -04:00
return ;
error :
raop_session_failure ( rs ) ;
}
/* Volume in [0 - 100] */
2016-01-23 19:14:07 -05:00
static int
raop_set_volume_one ( struct output_device * rd , output_status_cb cb )
2010-05-02 05:32:16 -04:00
{
2016-01-23 19:14:07 -05:00
struct raop_session * rs ;
2010-05-02 05:32:16 -04:00
int ret ;
2016-01-23 19:14:07 -05:00
if ( ! rd - > session | | ! rd - > session - > session )
return 0 ;
rs = rd - > session - > session ;
2016-02-12 15:59:26 -05:00
if ( ! ( rs - > state & RAOP_STATE_F_CONNECTED ) )
2010-11-19 15:49:17 -05:00
return 0 ;
2010-05-02 05:32:16 -04:00
2016-01-23 19:14:07 -05:00
ret = raop_set_volume_internal ( rs , rd - > volume , raop_cb_set_volume ) ;
2010-11-19 15:49:17 -05:00
if ( ret < 0 )
2010-05-02 05:32:16 -04:00
{
2010-11-19 15:49:17 -05:00
raop_session_failure ( rs ) ;
2010-05-02 05:32:16 -04:00
2010-11-19 15:49:17 -05:00
return 0 ;
2010-05-02 05:32:16 -04:00
}
2010-11-19 15:49:17 -05:00
rs - > status_cb = cb ;
2010-05-02 05:32:16 -04:00
2010-11-19 15:49:17 -05:00
return 1 ;
}
2010-05-02 05:32:16 -04:00
static void
2011-03-11 12:32:47 -05:00
raop_cb_flush ( struct evrtsp_request * req , void * arg )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto error ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " FLUSH request failed: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto error ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto error ;
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_CONNECTED ;
2010-05-02 05:32:16 -04:00
/* Let our user know */
2016-02-12 15:59:26 -05:00
raop_status ( rs ) ;
2010-05-02 05:32:16 -04:00
2011-03-11 14:02:29 -05:00
if ( ! rs - > reqs_in_flight )
evrtsp_connection_set_closecb ( rs - > ctrl , raop_rtsp_close_cb , rs ) ;
2010-05-02 05:32:16 -04:00
return ;
error :
raop_session_failure ( rs ) ;
}
2016-10-08 18:42:48 -04:00
static void
raop_cb_keep_alive ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
rs - > reqs_in_flight - - ;
if ( ! req )
{
2018-04-11 16:29:07 -04:00
DPRINTF ( E_LOG , L_RAOP , " No reply from '%s' to our keep alive request, hanging up \n " , rs - > devname ) ;
raop_session_failure ( rs ) ;
return ;
2016-10-08 18:42:48 -04:00
}
if ( ! rs - > reqs_in_flight )
evrtsp_connection_set_closecb ( rs - > ctrl , raop_rtsp_close_cb , rs ) ;
2018-04-11 16:29:07 -04:00
if ( req - > response_code ! = RTSP_OK )
{
2018-05-06 16:38:41 -04:00
DPRINTF ( E_LOG , L_RAOP , " Keep alive request to '%s' failed: %d %s \n " , rs - > devname , req - > response_code , req - > response_code_line ) ;
2018-04-11 16:29:07 -04:00
return ;
}
evtimer_add ( keep_alive_timer , & keep_alive_tv ) ;
return ;
2016-10-08 18:42:48 -04:00
}
2017-06-19 15:52:01 -04:00
static void
raop_cb_pin_start ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
int ret ;
rs - > reqs_in_flight - - ;
if ( ! req )
goto error ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " Request for starting PIN verification failed: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto error ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto error ;
rs - > state = RAOP_STATE_UNVERIFIED ;
raop_status ( rs ) ;
// TODO If the user never verifies the session will remain stale
return ;
error :
raop_session_failure ( rs ) ;
}
2016-01-23 19:14:07 -05:00
// Forward
static void
raop_device_stop ( struct output_session * session ) ;
2010-05-02 05:32:16 -04:00
static void
raop_flush_timer_cb ( int fd , short what , void * arg )
{
struct raop_session * rs ;
DPRINTF ( E_DBG , L_RAOP , " Flush timer expired; tearing down RAOP sessions \n " ) ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
2016-02-12 15:59:26 -05:00
if ( ! ( rs - > state & RAOP_STATE_F_CONNECTED ) )
2010-05-02 05:32:16 -04:00
continue ;
2016-01-23 19:14:07 -05:00
raop_device_stop ( rs - > output_session ) ;
2010-05-02 05:32:16 -04:00
}
}
2016-10-08 18:42:48 -04:00
static void
raop_keep_alive_timer_cb ( int fd , short what , void * arg )
{
struct raop_session * rs ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
if ( ! ( rs - > state & RAOP_STATE_F_CONNECTED ) )
continue ;
2018-05-06 16:38:41 -04:00
raop_send_req_options ( rs , raop_cb_keep_alive ) ;
2016-10-08 18:42:48 -04:00
}
}
2016-01-23 19:14:07 -05:00
static int
raop_flush ( output_status_cb cb , uint64_t rtptime )
2010-05-02 05:32:16 -04:00
{
struct timeval tv ;
struct raop_session * rs ;
2016-01-30 19:22:57 -05:00
struct raop_session * next ;
2010-05-02 05:32:16 -04:00
int pending ;
int ret ;
pending = 0 ;
2016-01-30 19:22:57 -05:00
for ( rs = sessions ; rs ; rs = next )
2010-05-02 05:32:16 -04:00
{
2016-01-30 19:22:57 -05:00
next = rs - > next ;
2016-02-12 15:59:26 -05:00
if ( rs - > state ! = RAOP_STATE_STREAMING )
2010-05-02 05:32:16 -04:00
continue ;
2011-03-11 12:32:47 -05:00
ret = raop_send_req_flush ( rs , rtptime , raop_cb_flush ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
raop_session_failure ( rs ) ;
continue ;
}
rs - > status_cb = cb ;
pending + + ;
}
if ( pending > 0 )
{
evutil_timerclear ( & tv ) ;
tv . tv_sec = 10 ;
2015-10-19 15:15:29 -04:00
evtimer_add ( flush_timer , & tv ) ;
2010-05-02 05:32:16 -04:00
}
return pending ;
}
/* AirTunes v2 time synchronization */
static void
raop_v2_timing_cb ( int fd , short what , void * arg )
{
union sockaddr_all sa ;
uint8_t req [ 32 ] ;
uint8_t res [ 32 ] ;
struct ntp_stamp recv_stamp ;
struct ntp_stamp xmit_stamp ;
2010-05-14 11:38:05 -04:00
struct raop_service * svc ;
2010-05-02 05:32:16 -04:00
int len ;
int ret ;
2010-05-14 11:38:05 -04:00
svc = ( struct raop_service * ) arg ;
2010-05-02 05:32:16 -04:00
ret = raop_v2_timing_get_clock_ntp ( & recv_stamp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't get receive timestamp \n " ) ;
2010-05-13 11:16:44 -04:00
goto readd ;
2010-05-02 05:32:16 -04:00
}
len = sizeof ( sa . ss ) ;
2010-05-14 11:38:05 -04:00
ret = recvfrom ( svc - > fd , req , sizeof ( req ) , 0 , & sa . sa , ( socklen_t * ) & len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Error reading timing request: %s \n " , strerror ( errno ) ) ;
2010-05-13 11:16:44 -04:00
goto readd ;
2010-05-02 05:32:16 -04:00
}
if ( ret ! = 32 )
{
DPRINTF ( E_DBG , L_RAOP , " Got timing request with size %d \n " , ret ) ;
2010-05-13 11:16:44 -04:00
goto readd ;
2010-05-02 05:32:16 -04:00
}
if ( ( req [ 0 ] ! = 0x80 ) | | ( req [ 1 ] ! = 0xd2 ) )
{
DPRINTF ( E_LOG , L_RAOP , " Packet header doesn't match timing request \n " ) ;
2010-05-13 11:16:44 -04:00
goto readd ;
2010-05-02 05:32:16 -04:00
}
memset ( res , 0 , sizeof ( res ) ) ;
/* Header */
res [ 0 ] = 0x80 ;
res [ 1 ] = 0xd3 ;
res [ 2 ] = req [ 2 ] ;
/* Copy client timestamp */
memcpy ( res + 8 , req + 24 , 8 ) ;
/* Receive timestamp */
recv_stamp . sec = htobe32 ( recv_stamp . sec ) ;
recv_stamp . frac = htobe32 ( recv_stamp . frac ) ;
memcpy ( res + 16 , & recv_stamp . sec , 4 ) ;
memcpy ( res + 20 , & recv_stamp . frac , 4 ) ;
/* Transmit timestamp */
ret = raop_v2_timing_get_clock_ntp ( & xmit_stamp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't get transmit timestamp, falling back to receive timestamp \n " ) ;
/* Still better than failing altogether
* recv / xmit are close enough that it shouldn ' t matter much
*/
memcpy ( res + 24 , & recv_stamp . sec , 4 ) ;
memcpy ( res + 28 , & recv_stamp . frac , 4 ) ;
}
else
{
xmit_stamp . sec = htobe32 ( xmit_stamp . sec ) ;
xmit_stamp . frac = htobe32 ( xmit_stamp . frac ) ;
memcpy ( res + 24 , & xmit_stamp . sec , 4 ) ;
memcpy ( res + 28 , & xmit_stamp . frac , 4 ) ;
}
2010-05-14 11:38:05 -04:00
ret = sendto ( svc - > fd , res , sizeof ( res ) , 0 , & sa . sa , len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not send timing reply: %s \n " , strerror ( errno ) ) ;
2010-05-13 11:16:44 -04:00
goto readd ;
2010-05-02 05:32:16 -04:00
}
2010-05-13 11:16:44 -04:00
readd :
2015-10-19 15:15:29 -04:00
ret = event_add ( svc - > ev , NULL ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't re-add event for timing requests \n " ) ;
return ;
}
}
static int
2010-05-14 11:38:05 -04:00
raop_v2_timing_start_one ( struct raop_service * svc , int family )
2010-05-02 05:32:16 -04:00
{
union sockaddr_all sa ;
2010-05-14 11:38:05 -04:00
int on ;
2010-05-02 05:32:16 -04:00
int len ;
int ret ;
2017-01-06 03:44:18 -05:00
# ifdef SOCK_CLOEXEC
2010-05-14 11:38:05 -04:00
svc - > fd = socket ( family , SOCK_DGRAM | SOCK_CLOEXEC , 0 ) ;
2010-05-02 05:32:16 -04:00
# else
2010-05-14 11:38:05 -04:00
svc - > fd = socket ( family , SOCK_DGRAM , 0 ) ;
2010-05-02 05:32:16 -04:00
# endif
2010-05-14 11:38:05 -04:00
if ( svc - > fd < 0 )
2010-05-02 05:32:16 -04:00
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't make timing socket: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
2010-05-14 11:38:05 -04:00
if ( family = = AF_INET6 )
{
on = 1 ;
ret = setsockopt ( svc - > fd , IPPROTO_IPV6 , IPV6_V6ONLY , & on , sizeof ( on ) ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not set IPV6_V6ONLY on timing socket: %s \n " , strerror ( errno ) ) ;
goto out_fail ;
}
}
2011-07-09 05:49:02 -04:00
memset ( & sa , 0 , sizeof ( union sockaddr_all ) ) ;
2010-05-14 11:38:05 -04:00
sa . ss . ss_family = family ;
switch ( family )
{
case AF_INET :
sa . sin . sin_addr . s_addr = INADDR_ANY ;
sa . sin . sin_port = 0 ;
len = sizeof ( sa . sin ) ;
break ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
case AF_INET6 :
sa . sin6 . sin6_addr = in6addr_any ;
sa . sin6 . sin6_port = 0 ;
len = sizeof ( sa . sin6 ) ;
break ;
}
ret = bind ( svc - > fd , & sa . sa , len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't bind timing socket: %s \n " , strerror ( errno ) ) ;
2010-05-14 11:38:05 -04:00
goto out_fail ;
2010-05-02 05:32:16 -04:00
}
len = sizeof ( sa . ss ) ;
2010-05-14 11:38:05 -04:00
ret = getsockname ( svc - > fd , & sa . sa , ( socklen_t * ) & len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't get timing socket name: %s \n " , strerror ( errno ) ) ;
2010-05-14 11:38:05 -04:00
goto out_fail ;
2010-05-02 05:32:16 -04:00
}
2010-05-14 11:38:05 -04:00
switch ( family )
{
case AF_INET :
svc - > port = ntohs ( sa . sin . sin_port ) ;
DPRINTF ( E_DBG , L_RAOP , " Timing IPv4 port: %d \n " , svc - > port ) ;
break ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
case AF_INET6 :
svc - > port = ntohs ( sa . sin6 . sin6_port ) ;
DPRINTF ( E_DBG , L_RAOP , " Timing IPv6 port: %d \n " , svc - > port ) ;
break ;
}
2010-05-02 05:32:16 -04:00
2015-10-19 15:15:29 -04:00
svc - > ev = event_new ( evbase_player , svc - > fd , EV_READ , raop_v2_timing_cb , svc ) ;
if ( ! svc - > ev )
2010-05-02 05:32:16 -04:00
{
2015-10-19 15:15:29 -04:00
DPRINTF ( E_LOG , L_RAOP , " Out of memory for raop_service event \n " ) ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
goto out_fail ;
2010-05-02 05:32:16 -04:00
}
2015-10-19 15:15:29 -04:00
event_add ( svc - > ev , NULL ) ;
2010-05-02 05:32:16 -04:00
return 0 ;
2010-05-14 11:38:05 -04:00
out_fail :
close ( svc - > fd ) ;
svc - > fd = - 1 ;
svc - > port = 0 ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
static void
raop_v2_timing_stop ( void )
{
2015-10-19 15:15:29 -04:00
if ( timing_4svc . ev )
event_free ( timing_4svc . ev ) ;
2010-05-14 11:38:05 -04:00
2015-10-19 15:15:29 -04:00
if ( timing_6svc . ev )
event_free ( timing_6svc . ev ) ;
2010-05-14 11:38:05 -04:00
close ( timing_4svc . fd ) ;
timing_4svc . fd = - 1 ;
timing_4svc . port = 0 ;
close ( timing_6svc . fd ) ;
timing_6svc . fd = - 1 ;
timing_6svc . port = 0 ;
}
static int
2011-03-20 07:20:07 -04:00
raop_v2_timing_start ( int v6enabled )
2010-05-14 11:38:05 -04:00
{
int ret ;
2010-05-02 05:32:16 -04:00
2011-03-20 07:20:07 -04:00
if ( v6enabled )
{
ret = raop_v2_timing_start_one ( & timing_6svc , AF_INET6 ) ;
if ( ret < 0 )
DPRINTF ( E_WARN , L_RAOP , " Could not start timing service on IPv6 \n " ) ;
}
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
ret = raop_v2_timing_start_one ( & timing_4svc , AF_INET ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not start timing service on IPv4 \n " ) ;
raop_v2_timing_stop ( ) ;
return - 1 ;
}
return 0 ;
2010-05-02 05:32:16 -04:00
}
/* AirTunes v2 playback synchronization */
static void
raop_v2_control_send_sync ( uint64_t next_pkt , struct timespec * init )
{
uint8_t msg [ 20 ] ;
struct timespec ts ;
struct ntp_stamp cur_stamp ;
struct raop_session * rs ;
uint64_t cur_pos ;
uint32_t cur_pos32 ;
uint32_t next_pkt32 ;
2010-05-14 11:38:05 -04:00
int len ;
2010-05-02 05:32:16 -04:00
int ret ;
memset ( msg , 0 , sizeof ( msg ) ) ;
msg [ 0 ] = ( sync_counter = = 0 ) ? 0x90 : 0x80 ;
msg [ 1 ] = 0xd4 ;
msg [ 3 ] = 0x07 ;
next_pkt32 = htobe32 ( RAOP_RTPTIME ( next_pkt ) ) ;
memcpy ( msg + 16 , & next_pkt32 , 4 ) ;
if ( ! init )
{
ret = player_get_current_pos ( & cur_pos , & ts , 1 ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not get current playback position and clock \n " ) ;
return ;
}
timespec_to_ntp ( & ts , & cur_stamp ) ;
}
else
{
cur_pos = next_pkt - 88200 ;
timespec_to_ntp ( init , & cur_stamp ) ;
}
cur_pos32 = htobe32 ( RAOP_RTPTIME ( cur_pos ) ) ;
cur_stamp . sec = htobe32 ( cur_stamp . sec ) ;
cur_stamp . frac = htobe32 ( cur_stamp . frac ) ;
memcpy ( msg + 4 , & cur_pos32 , 4 ) ;
memcpy ( msg + 8 , & cur_stamp . sec , 4 ) ;
memcpy ( msg + 12 , & cur_stamp . frac , 4 ) ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
2016-02-12 15:59:26 -05:00
if ( rs - > state ! = RAOP_STATE_STREAMING )
2010-05-02 05:32:16 -04:00
continue ;
2010-05-14 11:38:05 -04:00
switch ( rs - > sa . ss . ss_family )
{
case AF_INET :
rs - > sa . sin . sin_port = htons ( rs - > control_port ) ;
len = sizeof ( rs - > sa . sin ) ;
break ;
case AF_INET6 :
rs - > sa . sin6 . sin6_port = htons ( rs - > control_port ) ;
len = sizeof ( rs - > sa . sin6 ) ;
break ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
default :
DPRINTF ( E_WARN , L_RAOP , " Unknown family %d \n " , rs - > sa . ss . ss_family ) ;
continue ;
}
ret = sendto ( rs - > control_svc - > fd , msg , sizeof ( msg ) , 0 , & rs - > sa . sa , len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Could not send playback sync to device '%s': %s \n " , rs - > devname , strerror ( errno ) ) ;
2010-05-02 05:32:16 -04:00
}
}
2010-10-05 14:19:08 -04:00
/* Forward */
static void
raop_v2_resend_range ( struct raop_session * rs , uint16_t seqnum , uint16_t len ) ;
2010-10-05 13:48:22 -04:00
static void
raop_v2_control_cb ( int fd , short what , void * arg )
{
char address [ INET6_ADDRSTRLEN ] ;
union sockaddr_all sa ;
uint8_t req [ 8 ] ;
struct raop_session * rs ;
struct raop_service * svc ;
uint16_t seq_start ;
uint16_t seq_len ;
int len ;
int ret ;
svc = ( struct raop_service * ) arg ;
len = sizeof ( sa . ss ) ;
ret = recvfrom ( svc - > fd , req , sizeof ( req ) , 0 , & sa . sa , ( socklen_t * ) & len ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Error reading control request: %s \n " , strerror ( errno ) ) ;
goto readd ;
}
if ( ret ! = 8 )
{
DPRINTF ( E_DBG , L_RAOP , " Got control request with size %d \n " , ret ) ;
goto readd ;
}
switch ( sa . ss . ss_family )
{
case AF_INET :
if ( svc ! = & control_4svc )
goto readd ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
if ( ( rs - > sa . ss . ss_family = = AF_INET )
& & ( sa . sin . sin_addr . s_addr = = rs - > sa . sin . sin_addr . s_addr ) )
break ;
}
if ( ! rs )
ret = ( inet_ntop ( AF_INET , & sa . sin . sin_addr . s_addr , address , sizeof ( address ) ) ! = NULL ) ;
break ;
case AF_INET6 :
if ( svc ! = & control_6svc )
goto readd ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
if ( ( rs - > sa . ss . ss_family = = AF_INET6 )
2011-02-24 13:51:37 -05:00
& & IN6_ARE_ADDR_EQUAL ( & sa . sin6 . sin6_addr , & rs - > sa . sin6 . sin6_addr ) )
2010-10-05 13:48:22 -04:00
break ;
}
if ( ! rs )
ret = ( inet_ntop ( AF_INET6 , & sa . sin6 . sin6_addr . s6_addr , address , sizeof ( address ) ) ! = NULL ) ;
break ;
default :
DPRINTF ( E_LOG , L_RAOP , " Control svc: Unknown address family %d \n " , sa . ss . ss_family ) ;
goto readd ;
}
if ( ! rs )
{
if ( ! ret )
DPRINTF ( E_LOG , L_RAOP , " Control request from [error: %s]; not a RAOP client \n " , strerror ( errno ) ) ;
else
DPRINTF ( E_LOG , L_RAOP , " Control request from %s; not a RAOP client \n " , address ) ;
goto readd ;
}
if ( ( req [ 0 ] ! = 0x80 ) | | ( req [ 1 ] ! = 0xd5 ) )
{
DPRINTF ( E_LOG , L_RAOP , " Packet header doesn't match retransmit request \n " ) ;
goto readd ;
}
memcpy ( & seq_start , req + 4 , 2 ) ;
memcpy ( & seq_len , req + 6 , 2 ) ;
seq_start = be16toh ( seq_start ) ;
seq_len = be16toh ( seq_len ) ;
2017-07-09 05:18:55 -04:00
DPRINTF ( E_DBG , L_RAOP , " Got retransmit request from '%s', seq_start %u len %u \n " , rs - > devname , seq_start , seq_len ) ;
2010-10-05 13:48:22 -04:00
2010-10-05 14:19:08 -04:00
raop_v2_resend_range ( rs , seq_start , seq_len ) ;
2010-10-05 13:48:22 -04:00
readd :
2015-10-19 15:15:29 -04:00
ret = event_add ( svc - > ev , NULL ) ;
2010-10-05 13:48:22 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't re-add event for control requests \n " ) ;
return ;
}
}
2010-05-02 05:32:16 -04:00
static int
2010-05-14 11:38:05 -04:00
raop_v2_control_start_one ( struct raop_service * svc , int family )
2010-05-02 05:32:16 -04:00
{
union sockaddr_all sa ;
2010-05-14 11:38:05 -04:00
int on ;
2010-05-02 05:32:16 -04:00
int len ;
int ret ;
2017-01-06 03:44:18 -05:00
# ifdef SOCK_CLOEXEC
2010-05-14 11:38:05 -04:00
svc - > fd = socket ( family , SOCK_DGRAM | SOCK_CLOEXEC , 0 ) ;
2010-05-02 05:32:16 -04:00
# else
2010-05-14 11:38:05 -04:00
svc - > fd = socket ( family , SOCK_DGRAM , 0 ) ;
2010-05-02 05:32:16 -04:00
# endif
2010-05-14 11:38:05 -04:00
if ( svc - > fd < 0 )
2010-05-02 05:32:16 -04:00
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't make control socket: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
2010-05-14 11:38:05 -04:00
if ( family = = AF_INET6 )
{
on = 1 ;
ret = setsockopt ( svc - > fd , IPPROTO_IPV6 , IPV6_V6ONLY , & on , sizeof ( on ) ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not set IPV6_V6ONLY on control socket: %s \n " , strerror ( errno ) ) ;
goto out_fail ;
}
}
2011-07-09 05:49:02 -04:00
memset ( & sa , 0 , sizeof ( union sockaddr_all ) ) ;
2010-05-14 11:38:05 -04:00
sa . ss . ss_family = family ;
switch ( family )
{
case AF_INET :
sa . sin . sin_addr . s_addr = INADDR_ANY ;
sa . sin . sin_port = 0 ;
len = sizeof ( sa . sin ) ;
break ;
case AF_INET6 :
sa . sin6 . sin6_addr = in6addr_any ;
sa . sin6 . sin6_port = 0 ;
len = sizeof ( sa . sin6 ) ;
break ;
}
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
ret = bind ( svc - > fd , & sa . sa , len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't bind control socket: %s \n " , strerror ( errno ) ) ;
2010-05-14 11:38:05 -04:00
goto out_fail ;
2010-05-02 05:32:16 -04:00
}
len = sizeof ( sa . ss ) ;
2010-05-14 11:38:05 -04:00
ret = getsockname ( svc - > fd , & sa . sa , ( socklen_t * ) & len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't get control socket name: %s \n " , strerror ( errno ) ) ;
2010-05-14 11:38:05 -04:00
goto out_fail ;
2010-05-02 05:32:16 -04:00
}
2010-05-14 11:38:05 -04:00
switch ( family )
{
case AF_INET :
svc - > port = ntohs ( sa . sin . sin_port ) ;
DPRINTF ( E_DBG , L_RAOP , " Control IPv4 port: %d \n " , svc - > port ) ;
break ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
case AF_INET6 :
svc - > port = ntohs ( sa . sin6 . sin6_port ) ;
DPRINTF ( E_DBG , L_RAOP , " Control IPv6 port: %d \n " , svc - > port ) ;
break ;
}
2010-05-02 05:32:16 -04:00
2015-10-19 15:15:29 -04:00
svc - > ev = event_new ( evbase_player , svc - > fd , EV_READ , raop_v2_control_cb , svc ) ;
if ( ! svc - > ev )
2010-10-05 13:48:22 -04:00
{
2015-10-19 15:15:29 -04:00
DPRINTF ( E_LOG , L_RAOP , " Out of memory for control event \n " ) ;
2010-10-05 13:48:22 -04:00
goto out_fail ;
}
2015-10-19 15:15:29 -04:00
event_add ( svc - > ev , NULL ) ;
2010-05-02 05:32:16 -04:00
return 0 ;
2010-05-14 11:38:05 -04:00
out_fail :
close ( svc - > fd ) ;
svc - > fd = - 1 ;
svc - > port = 0 ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
static void
raop_v2_control_stop ( void )
{
2015-10-19 15:15:29 -04:00
if ( control_4svc . ev )
event_free ( control_4svc . ev ) ;
2010-10-05 13:48:22 -04:00
2015-10-19 15:15:29 -04:00
if ( control_6svc . ev )
event_free ( control_6svc . ev ) ;
2010-10-05 13:48:22 -04:00
2010-05-14 11:38:05 -04:00
close ( control_4svc . fd ) ;
control_4svc . fd = - 1 ;
control_4svc . port = 0 ;
close ( control_6svc . fd ) ;
control_6svc . fd = - 1 ;
control_6svc . port = 0 ;
}
static int
2011-03-20 07:20:07 -04:00
raop_v2_control_start ( int v6enabled )
2010-05-14 11:38:05 -04:00
{
int ret ;
2011-03-20 07:20:07 -04:00
if ( v6enabled )
{
ret = raop_v2_control_start_one ( & control_6svc , AF_INET6 ) ;
if ( ret < 0 )
DPRINTF ( E_WARN , L_RAOP , " Could not start control service on IPv6 \n " ) ;
}
2010-05-14 11:38:05 -04:00
ret = raop_v2_control_start_one ( & control_4svc , AF_INET ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not start control service on IPv4 \n " ) ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
raop_v2_control_stop ( ) ;
return - 1 ;
}
return 0 ;
2010-05-02 05:32:16 -04:00
}
/* AirTunes v2 streaming */
2010-10-05 14:02:42 -04:00
static struct raop_v2_packet *
raop_v2_new_packet ( void )
{
struct raop_v2_packet * pkt ;
if ( pktbuf_size > = RETRANSMIT_BUFFER_SIZE )
{
pktbuf_size - - ;
pkt = pktbuf_tail ;
pktbuf_tail = pktbuf_tail - > prev ;
pktbuf_tail - > next = NULL ;
}
else
{
pkt = ( struct raop_v2_packet * ) malloc ( sizeof ( struct raop_v2_packet ) ) ;
if ( ! pkt )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for RAOP packet \n " ) ;
return NULL ;
}
}
return pkt ;
}
static struct raop_v2_packet *
raop_v2_make_packet ( uint8_t * rawbuf , uint64_t rtptime )
2010-05-02 05:32:16 -04:00
{
char ebuf [ 64 ] ;
2010-10-05 14:02:42 -04:00
struct raop_v2_packet * pkt ;
2010-05-02 05:32:16 -04:00
gpg_error_t gc_err ;
uint32_t rtptime32 ;
uint16_t seq ;
2010-10-05 14:02:42 -04:00
pkt = raop_v2_new_packet ( ) ;
if ( ! pkt )
return NULL ;
2010-05-02 05:32:16 -04:00
memset ( pkt , 0 , sizeof ( struct raop_v2_packet ) ) ;
2010-07-30 12:52:14 -04:00
alac_encode ( rawbuf , pkt - > clear + AIRTUNES_V2_HDR_LEN , STOB ( AIRTUNES_V2_PACKET_SAMPLES ) ) ;
2010-05-02 05:32:16 -04:00
stream_seq + + ;
2010-10-05 14:02:42 -04:00
pkt - > seqnum = stream_seq ;
seq = htobe16 ( pkt - > seqnum ) ;
2010-05-02 05:32:16 -04:00
rtptime32 = htobe32 ( RAOP_RTPTIME ( rtptime ) ) ;
2010-07-30 12:52:14 -04:00
pkt - > clear [ 0 ] = 0x80 ;
pkt - > clear [ 1 ] = ( sync_counter = = 0 ) ? 0xe0 : 0x60 ;
2010-05-02 05:32:16 -04:00
2010-07-30 12:52:14 -04:00
memcpy ( pkt - > clear + 2 , & seq , 2 ) ;
memcpy ( pkt - > clear + 4 , & rtptime32 , 4 ) ;
2010-05-02 05:32:16 -04:00
2010-10-03 11:01:08 -04:00
/* RTP SSRC ID
* Note : should htobe32 ( ) that value , but it ' s just a
* random / unique ID so it ' s no big deal
*/
memcpy ( pkt - > clear + 8 , & ssrc_id , 4 ) ;
2010-05-02 05:32:16 -04:00
2010-07-30 12:52:14 -04:00
/* Copy AirTunes v2 header to encrypted packet */
memcpy ( pkt - > encrypted , pkt - > clear , AIRTUNES_V2_HDR_LEN ) ;
/* Copy the tail of the audio packet that is left unencrypted */
memcpy ( pkt - > encrypted + AIRTUNES_V2_PKT_TAIL_OFF ,
pkt - > clear + AIRTUNES_V2_PKT_TAIL_OFF ,
AIRTUNES_V2_PKT_TAIL_LEN ) ;
2010-05-02 05:32:16 -04:00
/* Reset cipher */
gc_err = gcry_cipher_reset ( raop_aes_ctx ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not reset AES cipher: %s \n " , ebuf ) ;
2010-10-05 14:02:42 -04:00
free ( pkt ) ;
return NULL ;
2010-05-02 05:32:16 -04:00
}
/* Set IV */
gc_err = gcry_cipher_setiv ( raop_aes_ctx , raop_aes_iv , sizeof ( raop_aes_iv ) ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not set AES IV: %s \n " , ebuf ) ;
2010-10-05 14:02:42 -04:00
free ( pkt ) ;
return NULL ;
2010-05-02 05:32:16 -04:00
}
2010-07-30 12:34:59 -04:00
/* Encrypt in blocks of 16 bytes */
gc_err = gcry_cipher_encrypt ( raop_aes_ctx ,
2010-07-30 12:52:14 -04:00
pkt - > encrypted + AIRTUNES_V2_HDR_LEN , ( ( AIRTUNES_V2_PKT_LEN - AIRTUNES_V2_HDR_LEN ) / 16 ) * 16 ,
pkt - > clear + AIRTUNES_V2_HDR_LEN , ( ( AIRTUNES_V2_PKT_LEN - AIRTUNES_V2_HDR_LEN ) / 16 ) * 16 ) ;
2010-05-02 05:32:16 -04:00
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not encrypt payload: %s \n " , ebuf ) ;
2010-10-05 14:02:42 -04:00
free ( pkt ) ;
return NULL ;
2010-05-02 05:32:16 -04:00
}
2010-10-05 14:02:42 -04:00
pkt - > prev = NULL ;
pkt - > next = pktbuf_head ;
if ( pktbuf_head )
pktbuf_head - > prev = pkt ;
if ( ! pktbuf_tail )
pktbuf_tail = pkt ;
pktbuf_head = pkt ;
pktbuf_size + + ;
return pkt ;
2010-05-02 05:32:16 -04:00
}
2010-10-05 13:52:07 -04:00
static int
raop_v2_send_packet ( struct raop_session * rs , struct raop_v2_packet * pkt )
{
uint8_t * data ;
int ret ;
2016-01-24 15:05:52 -05:00
if ( ! rs )
return - 1 ;
2010-10-05 13:52:07 -04:00
data = ( rs - > encrypt ) ? pkt - > encrypted : pkt - > clear ;
ret = send ( rs - > server_fd , data , AIRTUNES_V2_PKT_LEN , 0 ) ;
if ( ret < 0 )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Send error for '%s': %s \n " , rs - > devname , strerror ( errno ) ) ;
2010-10-05 13:52:07 -04:00
raop_session_failure ( rs ) ;
return - 1 ;
}
else if ( ret ! = AIRTUNES_V2_PKT_LEN )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_WARN , L_RAOP , " Partial send (%d) for '%s' \n " , ret , rs - > devname ) ;
2010-10-05 13:52:07 -04:00
return - 1 ;
}
return 0 ;
}
2016-01-23 19:14:07 -05:00
// Forward
static void
raop_playback_stop ( void ) ;
static void
2010-05-02 05:32:16 -04:00
raop_v2_write ( uint8_t * buf , uint64_t rtptime )
{
2010-10-05 14:02:42 -04:00
struct raop_v2_packet * pkt ;
2010-05-02 05:32:16 -04:00
struct raop_session * rs ;
2016-01-24 15:05:52 -05:00
struct raop_session * next ;
2010-05-02 05:32:16 -04:00
2010-10-05 14:02:42 -04:00
pkt = raop_v2_make_packet ( buf , rtptime ) ;
if ( ! pkt )
2010-05-02 05:32:16 -04:00
{
raop_playback_stop ( ) ;
return ;
}
if ( sync_counter = = 126 )
{
raop_v2_control_send_sync ( rtptime , NULL ) ;
sync_counter = 1 ;
}
else
sync_counter + + ;
2016-01-24 15:05:52 -05:00
for ( rs = sessions ; rs ; rs = next )
2010-05-02 05:32:16 -04:00
{
2016-01-24 15:05:52 -05:00
// raop_v2_send_packet may free rs on failure, so save rs->next now
next = rs - > next ;
2016-02-12 15:59:26 -05:00
if ( rs - > state ! = RAOP_STATE_STREAMING )
2010-05-02 05:32:16 -04:00
continue ;
2010-10-05 14:02:42 -04:00
raop_v2_send_packet ( rs , pkt ) ;
2010-05-02 05:32:16 -04:00
}
return ;
}
2010-10-05 14:19:08 -04:00
static void
raop_v2_resend_range ( struct raop_session * rs , uint16_t seqnum , uint16_t len )
{
struct raop_v2_packet * pktbuf ;
int ret ;
uint16_t distance ;
/* Check that seqnum is in the retransmit buffer */
2013-12-11 17:11:50 -05:00
if ( ( seqnum > pktbuf_head - > seqnum ) | | ( seqnum < pktbuf_tail - > seqnum ) )
2010-10-05 14:19:08 -04:00
{
2017-10-05 16:13:01 -04:00
DPRINTF ( E_WARN , L_RAOP , " Device '%s' asking for seqnum % " PRIu16 " ; not in buffer (h % " PRIu16 " t % " PRIu16 " ) \n " , rs - > devname , seqnum , pktbuf_head - > seqnum , pktbuf_tail - > seqnum ) ;
2010-10-05 14:19:08 -04:00
return ;
}
if ( seqnum > pktbuf_head - > seqnum )
{
distance = seqnum - pktbuf_tail - > seqnum ;
if ( distance > ( RETRANSMIT_BUFFER_SIZE / 2 ) )
pktbuf = pktbuf_head ;
else
pktbuf = pktbuf_tail ;
}
else
{
distance = pktbuf_head - > seqnum - seqnum ;
if ( distance > ( RETRANSMIT_BUFFER_SIZE / 2 ) )
pktbuf = pktbuf_tail ;
else
pktbuf = pktbuf_head ;
}
if ( pktbuf = = pktbuf_head )
{
2014-02-05 20:38:14 -05:00
while ( pktbuf & & seqnum ! = pktbuf - > seqnum )
2010-10-05 14:19:08 -04:00
pktbuf = pktbuf - > next ;
}
else
{
2014-02-05 20:38:14 -05:00
while ( pktbuf & & seqnum ! = pktbuf - > seqnum )
2010-10-05 14:19:08 -04:00
pktbuf = pktbuf - > prev ;
}
while ( len & & pktbuf )
{
ret = raop_v2_send_packet ( rs , pktbuf ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Error retransmit packet, aborting retransmission \n " ) ;
return ;
}
pktbuf = pktbuf - > prev ;
len - - ;
}
if ( len ! = 0 )
DPRINTF ( E_LOG , L_RAOP , " WARNING: len non-zero at end of retransmission \n " ) ;
}
2010-05-02 05:32:16 -04:00
static int
raop_v2_stream_open ( struct raop_session * rs )
{
2010-05-14 11:38:10 -04:00
int len ;
2010-05-02 05:32:16 -04:00
int ret ;
2017-01-06 03:44:18 -05:00
# ifdef SOCK_CLOEXEC
2010-05-14 11:38:10 -04:00
rs - > server_fd = socket ( rs - > sa . ss . ss_family , SOCK_DGRAM | SOCK_CLOEXEC , 0 ) ;
2010-05-02 05:32:16 -04:00
# else
2010-05-14 11:38:10 -04:00
rs - > server_fd = socket ( rs - > sa . ss . ss_family , SOCK_DGRAM , 0 ) ;
2010-05-02 05:32:16 -04:00
# endif
if ( rs - > server_fd < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create socket for streaming: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
2010-05-14 11:38:10 -04:00
switch ( rs - > sa . ss . ss_family )
{
case AF_INET :
rs - > sa . sin . sin_port = htons ( rs - > server_port ) ;
len = sizeof ( rs - > sa . sin ) ;
break ;
case AF_INET6 :
rs - > sa . sin6 . sin6_port = htons ( rs - > server_port ) ;
len = sizeof ( rs - > sa . sin6 ) ;
break ;
default :
DPRINTF ( E_WARN , L_RAOP , " Unknown family %d \n " , rs - > sa . ss . ss_family ) ;
goto out_fail ;
}
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:10 -04:00
ret = connect ( rs - > server_fd , & rs - > sa . sa , len ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
2010-05-14 11:38:10 -04:00
DPRINTF ( E_LOG , L_RAOP , " connect() to [%s]:%u failed: %s \n " , rs - > address , rs - > server_port , strerror ( errno ) ) ;
2010-05-02 05:32:16 -04:00
goto out_fail ;
}
/* Include the device into the set of active devices if
* playback is in progress .
*/
if ( sync_counter ! = 0 )
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_STREAMING ;
2010-05-02 05:32:16 -04:00
else
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_CONNECTED ;
2010-05-02 05:32:16 -04:00
return 0 ;
out_fail :
close ( rs - > server_fd ) ;
rs - > server_fd = - 1 ;
return - 1 ;
}
/* Session startup */
static void
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 ) ;
else
raop_session_failure ( rs ) ;
}
static void
raop_cb_startup_volume ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto cleanup ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " SET_PARAMETER request failed for startup volume: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto cleanup ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
2011-03-26 13:33:19 -04:00
raop_metadata_startup_send ( rs ) ;
2010-05-02 05:32:16 -04:00
ret = raop_v2_stream_open ( rs ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not open streaming socket \n " ) ;
goto cleanup ;
}
/* Session startup and setup is done, tell our user */
2016-02-12 15:59:26 -05:00
raop_status ( rs ) ;
2010-05-02 05:32:16 -04:00
2011-03-11 14:02:29 -05:00
if ( ! rs - > reqs_in_flight )
evrtsp_connection_set_closecb ( rs - > ctrl , raop_rtsp_close_cb , rs ) ;
2010-05-02 05:32:16 -04:00
return ;
cleanup :
raop_startup_cancel ( rs ) ;
}
static void
raop_cb_startup_record ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
const char * param ;
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto cleanup ;
if ( req - > response_code ! = RTSP_OK )
{
2011-03-04 14:47:36 -05:00
DPRINTF ( E_LOG , L_RAOP , " RECORD request failed in session startup: %d %s \n " , req - > response_code , req - > response_code_line ) ;
2010-05-02 05:32:16 -04:00
goto cleanup ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
/* Audio latency */
param = evrtsp_find_header ( req - > input_headers , " Audio-Latency " ) ;
if ( ! param )
2016-10-12 16:24:58 -04:00
DPRINTF ( E_INFO , L_RAOP , " RECORD reply from '%s' did not have an Audio-Latency header \n " , rs - > devname ) ;
2010-07-17 02:27:36 -04:00
else
DPRINTF ( E_DBG , L_RAOP , " RAOP audio latency is %s \n " , param ) ;
2010-05-02 05:32:16 -04:00
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_RECORD ;
2010-05-02 05:32:16 -04:00
/* Set initial volume */
2010-11-19 15:49:17 -05:00
raop_set_volume_internal ( rs , rs - > volume , raop_cb_startup_volume ) ;
2010-05-02 05:32:16 -04:00
return ;
cleanup :
raop_startup_cancel ( rs ) ;
}
static void
raop_cb_startup_setup ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
const char * param ;
char * transport ;
char * token ;
char * ptr ;
int tmp ;
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto cleanup ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " SETUP request failed in session startup: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto cleanup ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
/* Server-side session ID */
param = evrtsp_find_header ( req - > input_headers , " Session " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_RAOP , " Missing Session header in SETUP reply \n " ) ;
goto cleanup ;
}
rs - > session = strdup ( param ) ;
/* Check transport and get remote streaming port */
param = evrtsp_find_header ( req - > input_headers , " Transport " ) ;
if ( ! param )
{
DPRINTF ( E_LOG , L_RAOP , " Missing Transport header in SETUP reply \n " ) ;
goto cleanup ;
}
/* Check transport is really UDP, AirTunes v2 streaming */
if ( strncmp ( param , " RTP/AVP/UDP; " , strlen ( " RTP/AVP/UDP; " ) ) ! = 0 )
{
DPRINTF ( E_LOG , L_RAOP , " ApEx replied with unsupported Transport: %s \n " , param ) ;
goto cleanup ;
}
transport = strdup ( param ) ;
if ( ! transport )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for Transport header copy \n " ) ;
goto cleanup ;
}
token = strchr ( transport , ' ; ' ) ;
token + + ;
token = strtok_r ( token , " ;= " , & ptr ) ;
while ( token )
{
2011-03-21 13:41:38 -04:00
DPRINTF ( E_DBG , L_RAOP , " token: %s \n " , token ) ;
2010-05-02 05:32:16 -04:00
if ( strcmp ( token , " server_port " ) = = 0 )
{
token = strtok_r ( NULL , " ;= " , & ptr ) ;
if ( ! token )
break ;
ret = safe_atoi32 ( token , & tmp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read server_port \n " ) ;
break ;
}
rs - > server_port = tmp ;
}
else if ( strcmp ( token , " control_port " ) = = 0 )
{
token = strtok_r ( NULL , " ;= " , & ptr ) ;
if ( ! token )
break ;
ret = safe_atoi32 ( token , & tmp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read control_port \n " ) ;
break ;
}
rs - > control_port = tmp ;
}
else if ( strcmp ( token , " timing_port " ) = = 0 )
{
token = strtok_r ( NULL , " ;= " , & ptr ) ;
if ( ! token )
break ;
ret = safe_atoi32 ( token , & tmp ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not read timing_port \n " ) ;
break ;
}
rs - > timing_port = tmp ;
}
token = strtok_r ( NULL , " ;= " , & ptr ) ;
}
free ( transport ) ;
2017-09-26 15:41:53 -04:00
if ( ( rs - > server_port = = 0 ) | | ( rs - > control_port = = 0 ) )
2010-05-02 05:32:16 -04:00
{
DPRINTF ( E_LOG , L_RAOP , " Transport header lacked some port numbers in SETUP reply \n " ) ;
DPRINTF ( E_LOG , L_RAOP , " Transport header was: %s \n " , param ) ;
goto cleanup ;
}
DPRINTF ( E_DBG , L_RAOP , " Negotiated AirTunes v2 UDP streaming session %s; ports s=%u c=%u t=%u \n " , rs - > session , rs - > server_port , rs - > control_port , rs - > timing_port ) ;
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_SETUP ;
2010-05-02 05:32:16 -04:00
/* Send RECORD */
ret = raop_send_req_record ( rs , raop_cb_startup_record ) ;
if ( ret < 0 )
goto cleanup ;
return ;
cleanup :
raop_startup_cancel ( rs ) ;
}
static void
raop_cb_startup_announce ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto cleanup ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " ANNOUNCE request failed in session startup: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto cleanup ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_ANNOUNCE ;
2010-05-02 05:32:16 -04:00
/* Send SETUP */
ret = raop_send_req_setup ( rs , raop_cb_startup_setup ) ;
if ( ret < 0 )
goto cleanup ;
return ;
cleanup :
raop_startup_cancel ( rs ) ;
}
static void
raop_cb_startup_options ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2016-10-12 16:24:58 -04:00
if ( ! req | | ! req - > response_code )
{
DPRINTF ( E_LOG , L_RAOP , " No response from '%s' (%s) to OPTIONS request \n " , rs - > devname , rs - > address ) ;
2016-10-14 18:51:22 -04:00
if ( rs - > device - > v4_address & & ( rs - > sa . ss . ss_family = = AF_INET6 ) )
{
DPRINTF ( E_LOG , L_RAOP , " Falling back to ipv4, the ipv6 address is not responding \n " ) ;
free ( rs - > device - > v6_address ) ;
rs - > device - > v6_address = NULL ;
}
2016-10-12 16:24:58 -04:00
goto cleanup ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
if ( ( req - > response_code ! = RTSP_OK ) & & ( req - > response_code ! = RTSP_UNAUTHORIZED ) & & ( req - > response_code ! = RTSP_FORBIDDEN ) )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
DPRINTF ( E_LOG , L_RAOP , " OPTIONS request failed '%s': %d %s \n " , rs - > devname , req - > response_code , req - > response_code_line ) ;
2010-05-02 05:32:16 -04:00
goto cleanup ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
if ( req - > response_code = = RTSP_UNAUTHORIZED )
{
if ( rs - > req_has_auth )
{
2016-10-12 16:24:58 -04:00
DPRINTF ( E_LOG , L_RAOP , " Bad password for device '%s' \n " , rs - > devname ) ;
2010-05-02 05:32:16 -04:00
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_PASSWORD ;
2010-05-02 05:32:16 -04:00
goto cleanup ;
}
ret = raop_parse_auth ( rs , req ) ;
if ( ret < 0 )
goto cleanup ;
ret = raop_send_req_options ( rs , raop_cb_startup_options ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not re-run OPTIONS request with authentication \n " ) ;
2017-06-19 15:52:01 -04:00
goto cleanup ;
}
return ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
if ( req - > response_code = = RTSP_FORBIDDEN )
{
2017-11-19 14:11:42 -05:00
rs - > device - > requires_auth = 1 ;
2017-06-19 15:52:01 -04:00
ret = raop_send_req_pin_start ( rs , raop_cb_pin_start ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not request PIN for device verification \n " ) ;
2010-05-02 05:32:16 -04:00
goto cleanup ;
}
return ;
}
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_OPTIONS ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
if ( rs - > only_probe )
{
/* Device probed successfully, tell our user */
raop_status ( rs ) ;
/* We're not going further with this session */
raop_session_cleanup ( rs ) ;
}
else
{
/* Send ANNOUNCE */
ret = raop_send_req_announce ( rs , raop_cb_startup_announce ) ;
if ( ret < 0 )
goto cleanup ;
}
2010-05-02 05:32:16 -04:00
return ;
cleanup :
2017-06-19 15:52:01 -04:00
if ( rs - > only_probe )
raop_session_failure ( rs ) ;
else
raop_startup_cancel ( rs ) ;
2010-05-02 05:32:16 -04:00
}
static void
raop_cb_shutdown_teardown ( struct evrtsp_request * req , void * arg )
{
2017-06-19 15:52:01 -04:00
struct raop_session * rs = arg ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2010-05-02 05:32:16 -04:00
if ( ! req )
goto error ;
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " TEARDOWN request failed in session shutdown: %d %s \n " , req - > response_code , req - > response_code_line ) ;
goto error ;
}
ret = raop_check_cseq ( rs , req ) ;
if ( ret < 0 )
goto error ;
2016-02-12 15:59:26 -05:00
rs - > state = RAOP_STATE_STOPPED ;
2010-05-02 05:32:16 -04:00
/* Session shut down, tell our user */
2016-02-12 15:59:26 -05:00
raop_status ( rs ) ;
2010-05-02 05:32:16 -04:00
raop_session_cleanup ( rs ) ;
return ;
error :
raop_session_failure ( rs ) ;
}
2017-06-19 15:52:01 -04:00
/* tvOS device verification - e.g. for the ATV4 (read it from the bottom and up) */
# ifdef RAOP_VERIFICATION
static int
raop_verification_response_process ( int step , struct evrtsp_request * req , struct raop_session * rs )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
uint8_t * response ;
const char * errmsg ;
size_t len ;
2010-05-02 05:32:16 -04:00
int ret ;
2011-03-11 13:36:51 -05:00
rs - > reqs_in_flight - - ;
2017-06-19 15:52:01 -04:00
if ( ! req )
2016-10-12 16:24:58 -04:00
{
2017-06-19 15:52:01 -04:00
DPRINTF ( E_LOG , L_RAOP , " Verification step %d to '%s' failed, empty callback \n " , step , rs - > devname ) ;
return - 1 ;
}
2016-10-12 16:24:58 -04:00
2017-06-19 15:52:01 -04:00
if ( req - > response_code ! = RTSP_OK )
{
DPRINTF ( E_LOG , L_RAOP , " Verification step %d to '%s' failed with error code %d: %s \n " , step , rs - > devname , req - > response_code , req - > response_code_line ) ;
return - 1 ;
}
2016-10-14 18:51:22 -04:00
2017-06-19 15:52:01 -04:00
response = evbuffer_pullup ( req - > input_buffer , - 1 ) ;
len = evbuffer_get_length ( req - > input_buffer ) ;
2016-10-14 18:51:22 -04:00
2017-06-19 15:52:01 -04:00
switch ( step )
{
case 1 :
ret = verification_setup_response1 ( rs - > verification_setup_ctx , response , len ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
break ;
case 2 :
ret = verification_setup_response2 ( rs - > verification_setup_ctx , response , len ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
break ;
case 3 :
ret = verification_setup_response3 ( rs - > verification_setup_ctx , response , len ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
break ;
case 4 :
ret = verification_verify_response1 ( rs - > verification_verify_ctx , response , len ) ;
errmsg = verification_verify_errmsg ( rs - > verification_verify_ctx ) ;
break ;
case 5 :
ret = 0 ;
break ;
default :
ret = - 1 ;
errmsg = " Bug! Bad step number " ;
2016-10-12 16:24:58 -04:00
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " Verification step %d response from '%s' error: %s \n " , step , rs - > devname , errmsg ) ;
return ret ;
}
static int
raop_verification_request_send ( int step , struct raop_session * rs , void ( * cb ) ( struct evrtsp_request * , void * ) )
{
struct evrtsp_request * req ;
uint8_t * body ;
uint32_t len ;
const char * errmsg ;
const char * url ;
const char * ctype ;
int ret ;
switch ( step )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
case 1 :
body = verification_setup_request1 ( & len , rs - > verification_setup_ctx ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
url = " /pair-setup-pin " ;
ctype = " application/x-apple-binary-plist " ;
break ;
case 2 :
body = verification_setup_request2 ( & len , rs - > verification_setup_ctx ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
url = " /pair-setup-pin " ;
ctype = " application/x-apple-binary-plist " ;
break ;
case 3 :
body = verification_setup_request3 ( & len , rs - > verification_setup_ctx ) ;
errmsg = verification_setup_errmsg ( rs - > verification_setup_ctx ) ;
url = " /pair-setup-pin " ;
ctype = " application/x-apple-binary-plist " ;
break ;
case 4 :
body = verification_verify_request1 ( & len , rs - > verification_verify_ctx ) ;
errmsg = verification_verify_errmsg ( rs - > verification_verify_ctx ) ;
url = " /pair-verify " ;
ctype = " application/octet-stream " ;
break ;
case 5 :
body = verification_verify_request2 ( & len , rs - > verification_verify_ctx ) ;
errmsg = verification_verify_errmsg ( rs - > verification_verify_ctx ) ;
url = " /pair-verify " ;
ctype = " application/octet-stream " ;
break ;
default :
body = NULL ;
errmsg = " Bug! Bad step number " ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
if ( ! body )
{
DPRINTF ( E_LOG , L_RAOP , " Verification step %d request error: %s \n " , step , errmsg ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2017-06-19 15:52:01 -04:00
req = evrtsp_request_new ( cb , rs ) ;
if ( ! req )
{
DPRINTF ( E_LOG , L_RAOP , " Could not create RTSP request for verification step %d \n " , step ) ;
return - 1 ;
}
evbuffer_add ( req - > output_buffer , body , len ) ;
free ( body ) ;
ret = raop_add_headers ( rs , req , EVRTSP_REQ_POST ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
2017-06-19 15:52:01 -04:00
{
evrtsp_request_free ( req ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
evrtsp_add_header ( req - > output_headers , " Content-Type " , ctype ) ;
DPRINTF ( E_INFO , L_RAOP , " Making verification request step %d to '%s' \n " , step , rs - > devname ) ;
ret = evrtsp_make_request ( rs - > ctrl , req , EVRTSP_REQ_POST , url ) ;
if ( ret < 0 )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
DPRINTF ( E_LOG , L_RAOP , " Verification request step %d to '%s' failed \n " , step , rs - > devname ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
rs - > reqs_in_flight + + ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
evrtsp_connection_set_closecb ( rs - > ctrl , NULL , NULL ) ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
return 0 ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
static void
raop_cb_verification_verify_step2 ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
int ret ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
verification_verify_free ( rs - > verification_verify_ctx ) ;
ret = raop_verification_response_process ( 5 , req , rs ) ;
if ( ret < 0 )
2017-07-01 02:33:11 -04:00
{
// Clear auth_key, the device did not accept it
free ( rs - > device - > auth_key ) ;
rs - > device - > auth_key = NULL ;
goto error ;
}
2017-06-19 15:52:01 -04:00
2017-06-20 15:17:17 -04:00
DPRINTF ( E_INFO , L_RAOP , " Verification of '%s' completed succesfully \n " , rs - > devname ) ;
2017-06-19 15:52:01 -04:00
rs - > state = RAOP_STATE_STARTUP ;
raop_send_req_options ( rs , raop_cb_startup_options ) ;
2017-11-18 03:54:57 -05:00
player_speaker_status_trigger ( ) ;
2017-06-19 15:52:01 -04:00
return ;
error :
rs - > state = RAOP_STATE_UNVERIFIED ;
raop_status ( rs ) ;
}
static void
raop_cb_verification_verify_step1 ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
int ret ;
ret = raop_verification_response_process ( 4 , req , rs ) ;
if ( ret < 0 )
2017-07-01 02:33:11 -04:00
{
// Clear auth_key, the device did not accept it
free ( rs - > device - > auth_key ) ;
rs - > device - > auth_key = NULL ;
goto error ;
}
2017-06-19 15:52:01 -04:00
ret = raop_verification_request_send ( 5 , rs , raop_cb_verification_verify_step2 ) ;
if ( ret < 0 )
goto error ;
return ;
error :
rs - > state = RAOP_STATE_UNVERIFIED ;
raop_status ( rs ) ;
verification_verify_free ( rs - > verification_verify_ctx ) ;
rs - > verification_verify_ctx = NULL ;
}
static int
raop_verification_verify ( struct raop_session * rs )
{
int ret ;
rs - > verification_verify_ctx = verification_verify_new ( rs - > device - > auth_key ) ; // Naughty boy is dereferencing device
if ( ! rs - > verification_verify_ctx )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for verification verify context \n " ) ;
return - 1 ;
2010-05-02 05:32:16 -04:00
}
2017-06-19 15:52:01 -04:00
ret = raop_verification_request_send ( 4 , rs , raop_cb_verification_verify_step1 ) ;
if ( ret < 0 )
goto error ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
return 0 ;
error :
rs - > state = RAOP_STATE_UNVERIFIED ;
2016-02-12 15:59:26 -05:00
raop_status ( rs ) ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
verification_verify_free ( rs - > verification_verify_ctx ) ;
rs - > verification_verify_ctx = NULL ;
return - 1 ;
}
static void
raop_cb_verification_setup_step3 ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
const char * authorization_key ;
int ret ;
ret = raop_verification_response_process ( 3 , req , rs ) ;
if ( ret < 0 )
goto error ;
ret = verification_setup_result ( & authorization_key , rs - > verification_setup_ctx ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Verification setup result error: %s \n " , verification_setup_errmsg ( rs - > verification_setup_ctx ) ) ;
goto error ;
}
2017-06-20 15:36:21 -04:00
DPRINTF ( E_LOG , L_RAOP , " Verification setup stage complete, saving authorization key \n " ) ;
2017-06-19 15:52:01 -04:00
2017-06-24 17:59:02 -04:00
// Dereferencing output_device and a blocking db call... :-~
2017-06-19 15:52:01 -04:00
free ( rs - > device - > auth_key ) ;
rs - > device - > auth_key = strdup ( authorization_key ) ;
2017-06-24 17:59:02 -04:00
db_speaker_save ( rs - > device ) ;
2017-06-19 15:52:01 -04:00
// The player considers this session failed, so we don't need it any more
2010-05-02 05:32:16 -04:00
raop_session_cleanup ( rs ) ;
2017-06-19 15:52:01 -04:00
/* Fallthrough */
error :
verification_setup_free ( rs - > verification_setup_ctx ) ;
rs - > verification_setup_ctx = NULL ;
}
static void
raop_cb_verification_setup_step2 ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
int ret ;
ret = raop_verification_response_process ( 2 , req , rs ) ;
if ( ret < 0 )
goto error ;
ret = raop_verification_request_send ( 3 , rs , raop_cb_verification_setup_step3 ) ;
if ( ret < 0 )
goto error ;
2010-05-02 05:32:16 -04:00
return ;
2017-06-19 15:52:01 -04:00
error :
verification_setup_free ( rs - > verification_setup_ctx ) ;
rs - > verification_setup_ctx = NULL ;
}
static void
raop_cb_verification_setup_step1 ( struct evrtsp_request * req , void * arg )
{
struct raop_session * rs = arg ;
int ret ;
ret = raop_verification_response_process ( 1 , req , rs ) ;
if ( ret < 0 )
goto error ;
ret = raop_verification_request_send ( 2 , rs , raop_cb_verification_setup_step2 ) ;
if ( ret < 0 )
goto error ;
return ;
error :
verification_setup_free ( rs - > verification_setup_ctx ) ;
rs - > verification_setup_ctx = NULL ;
}
static void
raop_verification_setup ( const char * pin )
{
struct raop_session * rs ;
int ret ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
if ( rs - > state = = RAOP_STATE_UNVERIFIED )
break ;
}
if ( ! rs )
{
DPRINTF ( E_LOG , L_RAOP , " Got a PIN for device verification, but no device is awaiting verification \n " ) ;
return ;
}
rs - > verification_setup_ctx = verification_setup_new ( pin ) ;
if ( ! rs - > verification_setup_ctx )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory for verification setup context \n " ) ;
return ;
}
ret = raop_verification_request_send ( 1 , rs , raop_cb_verification_setup_step1 ) ;
if ( ret < 0 )
goto error ;
return ;
error :
verification_setup_free ( rs - > verification_setup_ctx ) ;
rs - > verification_setup_ctx = NULL ;
2010-05-02 05:32:16 -04:00
}
2017-06-19 15:52:01 -04:00
# else
static int
raop_verification_verify ( struct raop_session * rs )
{
DPRINTF ( E_LOG , L_RAOP , " Device '%s' requires verification, but forked-daapd was built with --disable-verification \n " , rs - > devname ) ;
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
return - 1 ;
}
# endif /* RAOP_VERIFICATION */
2010-05-02 05:32:16 -04:00
2016-01-23 19:14:07 -05:00
/* RAOP devices discovery - mDNS callback */
/* Thread: main (mdns) */
/* Examples of txt content:
* Apple TV 2 :
[ " sf=0x4 " " am=AppleTV2,1 " " vs=130.14 " " vn=65537 " " tp=UDP " " ss=16 " " sr=4 4100 " " sv=false " " pw=false " " md=0,1,2 " " et=0,3,5 " " da=true " " cn=0,1,2,3 " " ch=2 " ]
[ " sf=0x4 " " am=AppleTV2,1 " " vs=105.5 " " md=0,1,2 " " tp=TCP,UDP " " vn=65537 " " pw=false " " ss=16 " " sr=44100 " " da=true " " sv=false " " et=0,3 " " cn=0,1 " " ch=2 " " txtvers=1 " ]
* Apple TV 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 " ]
2016-10-08 18:42:48 -04:00
* 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 " ]
2016-01-23 19:14:07 -05:00
* 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 :
[ " rastx=iafs " " sm=false " " raver=3.5.3.0 " " ek=1 " " md=0,1,2 " " ramach=Win32NT.6 " " et=0,1 " " cn=0,1 " " sr=44100 " " ss=16 " " raAudioFormats=ALAC " " raflakyzeroconf=true " " pw=false " " rast=afs " " vn=3 " " sv=false " " txtvers=1 " " ch=2 " " tp=UDP " ]
* Xbmc 13 :
[ " am=Xbmc,1 " " md=0,1,2 " " vs=130.14 " " da=true " " vn=3 " " pw=false " " sr=44100 " " ss=16 " " sm=false " " tp=UDP " " sv=false " " et=0,1 " " ek=1 " " ch=2 " " cn=0,1 " " txtvers=1 " ]
* Shairport ( abrasive / 1.0 ) :
[ " pw=false " " txtvers=1 " " vn=3 " " sr=44100 " " ss=16 " " ch=2 " " cn=0,1 " " et=0,1 " " ek=1 " " sm=false " " tp=UDP " ]
* JB2 :
[ " fv=95.8947 " " am=JB2 Gen " " vs=103.2 " " tp=UDP " " vn=65537 " " pw=false " " s s=16 " " sr=44100 " " da=true " " sv=false " " et=0,4 " " cn=0,1 " " ch=2 " " txtvers=1 " ]
* Airport Express 802.11 g ( Gen 1 ) :
[ " tp=TCP,UDP " " sm=false " " sv=false " " ek=1 " " et=0,1 " " cn=0,1 " " ch=2 " " ss=16 " " sr=44100 " " pw=false " " vn=3 " " txtvers=1 " ]
* Airport Express 802.11 n :
802.11 n Gen 2 model ( firmware 7.6 .4 ) : " am=Airport4,107 " , " et=0,1 "
802.11 n Gen 3 model ( firmware 7.6 .4 ) : " am=Airport10,115 " , " et=0,4 "
*/
static void
raop_device_cb ( const char * name , const char * type , const char * domain , const char * hostname , int family , const char * address , int port , struct keyval * txt )
{
struct output_device * rd ;
struct raop_extra * re ;
cfg_t * airplay ;
const char * p ;
char * at_name ;
char * password ;
uint64_t id ;
2017-06-19 15:52:01 -04:00
uint64_t sf ;
2016-01-23 19:14:07 -05:00
int ret ;
ret = safe_hextou64 ( name , & id ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not extract AirPlay device ID (%s) \n " , name ) ;
return ;
}
at_name = strchr ( name , ' @ ' ) ;
if ( ! at_name )
{
DPRINTF ( E_LOG , L_RAOP , " Could not extract AirPlay device name (%s) \n " , name ) ;
return ;
}
at_name + + ;
DPRINTF ( E_DBG , L_RAOP , " Event for AirPlay device %s (port %d, id % " PRIx64 " ) \n " , at_name , port , id ) ;
2017-03-12 17:11:56 -04:00
airplay = cfg_gettsec ( cfg , " airplay " , at_name ) ;
if ( airplay & & cfg_getbool ( airplay , " exclude " ) )
{
DPRINTF ( E_LOG , L_RAOP , " Excluding AirPlay device '%s' as set in config \n " , at_name ) ;
return ;
}
2016-01-23 19:14:07 -05:00
rd = calloc ( 1 , sizeof ( struct output_device ) ) ;
2016-11-19 17:08:50 -05:00
if ( ! rd )
{
DPRINTF ( E_LOG , L_RAOP , " Out of memory (rd) \n " ) ;
return ;
}
2016-01-23 19:14:07 -05:00
re = calloc ( 1 , sizeof ( struct raop_extra ) ) ;
2016-11-19 17:08:50 -05:00
if ( ! re )
2016-01-23 19:14:07 -05:00
{
2016-11-19 17:08:50 -05:00
DPRINTF ( E_LOG , L_RAOP , " Out of memory (re) \n " ) ;
free ( rd ) ;
2016-01-23 19:14:07 -05:00
return ;
}
rd - > id = id ;
rd - > name = strdup ( at_name ) ;
rd - > type = OUTPUT_TYPE_RAOP ;
rd - > type_name = outputs_name ( rd - > type ) ;
rd - > extra_device_info = re ;
if ( port < 0 )
{
/* Device stopped advertising */
switch ( family )
{
case AF_INET :
rd - > v4_port = 1 ;
break ;
case AF_INET6 :
rd - > v6_port = 1 ;
break ;
}
ret = player_device_remove ( rd ) ;
if ( ret < 0 )
goto free_rd ;
return ;
}
/* Protocol */
p = keyval_get ( txt , " tp " ) ;
if ( ! p )
{
DPRINTF ( E_LOG , L_RAOP , " AirPlay %s: no tp field in TXT record! \n " , name ) ;
goto free_rd ;
}
if ( * p = = ' \0 ' )
{
DPRINTF ( E_LOG , L_RAOP , " AirPlay %s: tp has no value \n " , name ) ;
goto free_rd ;
}
if ( ! strstr ( p , " UDP " ) )
{
DPRINTF ( E_LOG , L_RAOP , " AirPlay %s: device does not support AirTunes v2 (tp=%s), discarding \n " , name , p ) ;
goto free_rd ;
}
/* Password protection */
password = NULL ;
p = keyval_get ( txt , " pw " ) ;
if ( ! p )
{
DPRINTF ( E_INFO , L_RAOP , " AirPlay %s: no pw field in TXT record, assuming no password protection \n " , name ) ;
rd - > has_password = 0 ;
}
else if ( * p = = ' \0 ' )
{
DPRINTF ( E_LOG , L_RAOP , " AirPlay %s: pw has no value \n " , name ) ;
goto free_rd ;
}
else
{
rd - > has_password = ( strcmp ( p , " false " ) ! = 0 ) ;
}
if ( rd - > has_password )
{
DPRINTF ( E_LOG , L_RAOP , " AirPlay device %s is password-protected \n " , name ) ;
airplay = cfg_gettsec ( cfg , " airplay " , at_name ) ;
if ( airplay )
password = cfg_getstr ( airplay , " password " ) ;
if ( ! password )
DPRINTF ( E_LOG , L_RAOP , " No password given in config for AirPlay device %s \n " , name ) ;
}
rd - > password = password ;
2017-06-19 15:52:01 -04:00
/* Device verification */
p = keyval_get ( txt , " sf " ) ;
if ( p & & ( safe_hextou64 ( p , & sf ) = = 0 ) )
{
if ( sf & ( 1 < < 9 ) )
rd - > requires_auth = 1 ;
// Note: device_add() in player.c will get the auth key from the db if available
}
2016-01-23 19:14:07 -05:00
/* Device type */
re - > devtype = RAOP_DEV_OTHER ;
p = keyval_get ( txt , " am " ) ;
if ( ! p )
re - > devtype = RAOP_DEV_APEX1_80211G ; // First generation AirPort Express
else if ( strncmp ( p , " AirPort4 " , strlen ( " AirPort4 " ) ) = = 0 )
re - > devtype = RAOP_DEV_APEX2_80211N ; // Second generation
else if ( strncmp ( p , " AirPort " , strlen ( " AirPort " ) ) = = 0 )
re - > devtype = RAOP_DEV_APEX3_80211N ; // Third generation and newer
2016-10-08 18:42:48 -04:00
else if ( strncmp ( p , " AppleTV5,3 " , strlen ( " AppleTV5,3 " ) ) = = 0 )
re - > devtype = RAOP_DEV_APPLETV4 ; // Stream to ATV with tvOS 10 needs to be kept alive
2016-01-23 19:14:07 -05:00
else if ( strncmp ( p , " AppleTV " , strlen ( " AppleTV " ) ) = = 0 )
re - > devtype = RAOP_DEV_APPLETV ;
else if ( * p = = ' \0 ' )
DPRINTF ( E_LOG , L_RAOP , " AirPlay %s: am has no value \n " , name ) ;
/* Encrypt stream */
p = keyval_get ( txt , " ek " ) ;
if ( p & & ( * p = = ' 1 ' ) )
re - > encrypt = 1 ;
else
re - > encrypt = 0 ;
/* Metadata support */
p = keyval_get ( txt , " md " ) ;
if ( p & & ( * p ! = ' \0 ' ) )
re - > wants_metadata = 1 ;
else
re - > wants_metadata = 0 ;
rd - > advertised = 1 ;
switch ( family )
{
case AF_INET :
rd - > v4_address = strdup ( address ) ;
rd - > v4_port = port ;
2017-06-19 15:52:01 -04:00
DPRINTF ( E_INFO , L_RAOP , " Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address %s:%d \n " ,
name , rd - > has_password , rd - > requires_auth , re - > encrypt , re - > wants_metadata , raop_devtype [ re - > devtype ] , address , port ) ;
2016-01-23 19:14:07 -05:00
break ;
case AF_INET6 :
rd - > v6_address = strdup ( address ) ;
rd - > v6_port = port ;
2017-06-19 15:52:01 -04:00
DPRINTF ( E_INFO , L_RAOP , " Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d \n " ,
name , rd - > has_password , rd - > requires_auth , re - > encrypt , re - > wants_metadata , raop_devtype [ re - > devtype ] , address , port ) ;
2016-01-23 19:14:07 -05:00
break ;
2016-10-12 16:24:58 -04:00
default :
DPRINTF ( E_LOG , L_RAOP , " Error: AirPlay device %s has neither ipv4 og ipv6 address \n " , name ) ;
goto free_rd ;
2016-01-23 19:14:07 -05:00
}
ret = player_device_add ( rd ) ;
if ( ret < 0 )
goto free_rd ;
return ;
free_rd :
outputs_device_free ( rd ) ;
}
static int
2017-06-19 15:52:01 -04:00
raop_device_start_generic ( struct output_device * rd , output_status_cb cb , uint64_t rtptime , bool only_probe )
2010-05-02 05:32:16 -04:00
{
struct raop_session * rs ;
int ret ;
2017-06-19 15:52:01 -04:00
/* Send an OPTIONS request to establish the connection. If device verification
* is required we start with that . After that , we can determine our local
* address and build our session URL for all subsequent requests .
2010-05-02 05:32:16 -04:00
*/
2010-05-14 11:38:16 -04:00
2017-06-19 15:52:01 -04:00
rs = raop_session_make ( rd , AF_INET6 , cb , only_probe ) ;
2010-05-14 11:38:16 -04:00
if ( rs )
{
2017-06-19 15:52:01 -04:00
rs - > start_rtptime = rtptime ;
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 ) ;
else
ret = raop_send_req_options ( rs , raop_cb_startup_options ) ;
2010-05-14 11:38:16 -04:00
if ( ret = = 0 )
return 0 ;
else
{
2017-06-19 15:52:01 -04:00
DPRINTF ( E_WARN , L_RAOP , " Could not send verification or OPTIONS request on IPv6 \n " ) ;
2010-05-14 11:38:16 -04:00
raop_session_cleanup ( rs ) ;
}
}
2017-06-19 15:52:01 -04:00
rs = raop_session_make ( rd , AF_INET , cb , only_probe ) ;
2010-05-14 11:38:16 -04:00
if ( ! rs )
return - 1 ;
2017-06-19 15:52:01 -04:00
rs - > start_rtptime = rtptime ;
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 ) ;
else
ret = raop_send_req_options ( rs , raop_cb_startup_options ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
2010-05-14 11:38:16 -04:00
{
2017-06-19 15:52:01 -04:00
DPRINTF ( E_WARN , L_RAOP , " Could not send verification or OPTIONS request on IPv4 \n " ) ;
2010-05-14 11:38:16 -04:00
raop_session_cleanup ( rs ) ;
return - 1 ;
}
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:16 -04:00
return 0 ;
2010-05-02 05:32:16 -04:00
}
2016-01-23 19:14:07 -05:00
static int
2017-06-19 15:52:01 -04:00
raop_device_probe ( struct output_device * rd , output_status_cb cb )
2010-05-02 05:32:16 -04:00
{
2017-06-19 15:52:01 -04:00
return raop_device_start_generic ( rd , cb , 0 , 1 ) ;
}
2010-05-02 05:32:16 -04:00
2017-06-19 15:52:01 -04:00
static int
raop_device_start ( struct output_device * rd , output_status_cb cb , uint64_t rtptime )
{
return raop_device_start_generic ( rd , cb , rtptime , 0 ) ;
2010-05-02 05:32:16 -04:00
}
2016-01-23 19:14:07 -05:00
static void
raop_device_stop ( struct output_session * session )
2010-05-02 05:32:16 -04:00
{
2016-01-23 19:14:07 -05:00
struct raop_session * rs = session - > session ;
2016-02-12 15:59:26 -05:00
if ( ! ( rs - > state & RAOP_STATE_F_CONNECTED ) )
2010-05-02 05:32:16 -04:00
raop_session_cleanup ( rs ) ;
else
raop_send_req_teardown ( rs , raop_cb_shutdown_teardown ) ;
}
2016-01-23 19:14:07 -05:00
static void
raop_device_free_extra ( struct output_device * device )
{
2017-06-19 15:52:01 -04:00
struct raop_extra * re = device - > extra_device_info ;
free ( re ) ;
2016-01-23 19:14:07 -05:00
}
static void
2010-05-02 05:32:16 -04:00
raop_playback_start ( uint64_t next_pkt , struct timespec * ts )
{
struct raop_session * rs ;
2015-10-19 15:15:29 -04:00
event_del ( flush_timer ) ;
2016-10-08 18:42:48 -04:00
evtimer_add ( keep_alive_timer , & keep_alive_tv ) ;
2010-05-02 05:32:16 -04:00
sync_counter = 0 ;
for ( rs = sessions ; rs ; rs = rs - > next )
{
2016-02-12 15:59:26 -05:00
if ( rs - > state = = RAOP_STATE_CONNECTED )
rs - > state = RAOP_STATE_STREAMING ;
2010-05-02 05:32:16 -04:00
}
/* Send initial playback sync */
raop_v2_control_send_sync ( next_pkt , ts ) ;
}
2016-01-23 19:14:07 -05:00
static void
2010-05-02 05:32:16 -04:00
raop_playback_stop ( void )
{
struct raop_session * rs ;
int ret ;
2016-10-08 18:42:48 -04:00
evtimer_del ( keep_alive_timer ) ;
2010-05-02 05:32:16 -04:00
for ( rs = sessions ; rs ; rs = rs - > next )
{
ret = raop_send_req_teardown ( rs , raop_cb_shutdown_teardown ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_RAOP , " shutdown: TEARDOWN request failed! \n " ) ;
}
}
2016-01-23 19:14:07 -05:00
static void
raop_set_status_cb ( struct output_session * session , output_status_cb cb )
2010-05-02 05:32:16 -04:00
{
2016-01-23 19:14:07 -05:00
struct raop_session * rs = session - > session ;
2010-05-02 05:32:16 -04:00
rs - > status_cb = cb ;
}
2016-01-23 19:14:07 -05:00
static int
raop_init ( void )
2010-05-02 05:32:16 -04:00
{
char ebuf [ 64 ] ;
char * ptr ;
2010-10-03 11:01:08 -04:00
char * libname ;
2010-05-02 05:32:16 -04:00
gpg_error_t gc_err ;
2016-01-23 19:14:07 -05:00
int v6enabled ;
2016-10-12 16:24:58 -04:00
int family ;
2010-05-02 05:32:16 -04:00
int ret ;
2010-05-14 11:38:05 -04:00
timing_4svc . fd = - 1 ;
timing_4svc . port = 0 ;
timing_6svc . fd = - 1 ;
timing_6svc . port = 0 ;
control_4svc . fd = - 1 ;
control_4svc . port = 0 ;
2010-05-02 05:32:16 -04:00
2010-05-14 11:38:05 -04:00
control_6svc . fd = - 1 ;
control_6svc . port = 0 ;
2010-05-02 05:32:16 -04:00
sessions = NULL ;
2010-10-05 14:02:42 -04:00
pktbuf_size = 0 ;
pktbuf_head = NULL ;
pktbuf_tail = NULL ;
2011-03-08 16:04:48 -05:00
metadata_head = NULL ;
metadata_tail = NULL ;
2010-10-03 11:01:08 -04:00
/* Generate RTP SSRC ID from library name */
libname = cfg_getstr ( cfg_getsec ( cfg , " library " ) , " name " ) ;
ssrc_id = djb_hash ( libname , strlen ( libname ) ) ;
2010-05-02 05:32:16 -04:00
/* Random RTP sequence start */
gcry_randomize ( & stream_seq , sizeof ( stream_seq ) , GCRY_STRONG_RANDOM ) ;
/* Generate AES key and IV */
gcry_randomize ( raop_aes_key , sizeof ( raop_aes_key ) , GCRY_STRONG_RANDOM ) ;
gcry_randomize ( raop_aes_iv , sizeof ( raop_aes_iv ) , GCRY_STRONG_RANDOM ) ;
/* Setup AES */
gc_err = gcry_cipher_open ( & raop_aes_ctx , GCRY_CIPHER_AES , GCRY_CIPHER_MODE_CBC , 0 ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not open AES cipher: %s \n " , ebuf ) ;
return - 1 ;
}
/* Set key */
gc_err = gcry_cipher_setkey ( raop_aes_ctx , raop_aes_key , sizeof ( raop_aes_key ) ) ;
if ( gc_err ! = GPG_ERR_NO_ERROR )
{
gpg_strerror_r ( gc_err , ebuf , sizeof ( ebuf ) ) ;
DPRINTF ( E_LOG , L_RAOP , " Could not set AES key: %s \n " , ebuf ) ;
goto out_close_cipher ;
}
/* Prepare Base64-encoded key & IV for SDP */
raop_aes_key_b64 = raop_crypt_encrypt_aes_key_base64 ( ) ;
if ( ! raop_aes_key_b64 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't encrypt and encode AES session key \n " ) ;
goto out_close_cipher ;
}
raop_aes_iv_b64 = b64_encode ( raop_aes_iv , sizeof ( raop_aes_iv ) ) ;
if ( ! raop_aes_iv_b64 )
{
DPRINTF ( E_LOG , L_RAOP , " Couldn't encode AES IV \n " ) ;
goto out_free_b64_key ;
}
/* Remove base64 padding */
ptr = strchr ( raop_aes_key_b64 , ' = ' ) ;
if ( ptr )
* ptr = ' \0 ' ;
ptr = strchr ( raop_aes_iv_b64 , ' = ' ) ;
if ( ptr )
* ptr = ' \0 ' ;
2015-10-19 15:15:29 -04:00
flush_timer = evtimer_new ( evbase_player , raop_flush_timer_cb , NULL ) ;
2016-10-08 18:42:48 -04:00
keep_alive_timer = evtimer_new ( evbase_player , raop_keep_alive_timer_cb , NULL ) ;
if ( ! flush_timer | | ! keep_alive_timer )
2015-10-19 15:15:29 -04:00
{
2016-10-08 18:42:48 -04:00
DPRINTF ( E_LOG , L_RAOP , " Out of memory for flush timer or keep alive timer \n " ) ;
2015-10-19 15:15:29 -04:00
goto out_free_b64_iv ;
}
2016-01-23 19:14:07 -05:00
v6enabled = cfg_getbool ( cfg_getsec ( cfg , " general " ) , " ipv6 " ) ;
ret = raop_v2_timing_start ( v6enabled ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
2016-01-30 19:22:57 -05:00
DPRINTF ( E_LOG , L_RAOP , " AirPlay time synchronization failed to start \n " ) ;
2010-05-02 05:32:16 -04:00
2016-10-08 18:42:48 -04:00
goto out_free_timers ;
2010-05-02 05:32:16 -04:00
}
2016-01-23 19:14:07 -05:00
ret = raop_v2_control_start ( v6enabled ) ;
2010-05-02 05:32:16 -04:00
if ( ret < 0 )
{
2016-10-08 18:42:48 -04:00
DPRINTF ( E_LOG , L_RAOP , " AirPlay playback control failed to start \n " ) ;
2010-05-02 05:32:16 -04:00
goto out_stop_timing ;
}
2016-01-23 19:14:07 -05:00
if ( v6enabled )
v6enabled = ! ( ( timing_6svc . fd < 0 ) | | ( control_6svc . fd < 0 ) ) ;
if ( v6enabled )
2016-10-12 16:24:58 -04:00
family = AF_UNSPEC ;
2016-01-23 19:14:07 -05:00
else
2016-10-12 16:24:58 -04:00
family = AF_INET ;
2016-01-23 19:14:07 -05:00
2016-10-12 16:24:58 -04:00
ret = mdns_browse ( " _raop._tcp " , family , raop_device_cb ) ;
2016-01-23 19:14:07 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_RAOP , " Could not add mDNS browser for AirPlay devices \n " ) ;
goto out_stop_control ;
}
2011-03-15 12:55:57 -04:00
2010-05-02 05:32:16 -04:00
return 0 ;
2016-01-23 19:14:07 -05:00
out_stop_control :
raop_v2_control_stop ( ) ;
2010-05-02 05:32:16 -04:00
out_stop_timing :
raop_v2_timing_stop ( ) ;
2016-10-08 18:42:48 -04:00
out_free_timers :
2015-10-19 15:15:29 -04:00
event_free ( flush_timer ) ;
2016-10-08 18:42:48 -04:00
event_free ( keep_alive_timer ) ;
2010-05-02 05:32:16 -04:00
out_free_b64_iv :
free ( raop_aes_iv_b64 ) ;
out_free_b64_key :
free ( raop_aes_key_b64 ) ;
out_close_cipher :
gcry_cipher_close ( raop_aes_ctx ) ;
return - 1 ;
}
2016-01-23 19:14:07 -05:00
static void
2010-05-02 05:32:16 -04:00
raop_deinit ( void )
{
2010-07-30 16:18:06 -04:00
struct raop_session * rs ;
for ( rs = sessions ; sessions ; rs = sessions )
{
sessions = rs - > next ;
raop_session_free ( rs ) ;
}
2010-05-02 05:32:16 -04:00
raop_v2_control_stop ( ) ;
2015-10-19 15:15:29 -04:00
raop_v2_timing_stop ( ) ;
event_free ( flush_timer ) ;
2016-10-08 18:42:48 -04:00
event_free ( keep_alive_timer ) ;
2010-05-02 05:32:16 -04:00
gcry_cipher_close ( raop_aes_ctx ) ;
free ( raop_aes_key_b64 ) ;
free ( raop_aes_iv_b64 ) ;
}
2016-01-23 19:14:07 -05:00
struct output_definition output_raop =
{
. name = " AirPlay " ,
. type = OUTPUT_TYPE_RAOP ,
. priority = 1 ,
. disabled = 0 ,
. init = raop_init ,
. deinit = raop_deinit ,
. device_start = raop_device_start ,
. device_stop = raop_device_stop ,
. device_probe = raop_device_probe ,
. device_free_extra = raop_device_free_extra ,
. device_volume_set = raop_set_volume_one ,
. playback_start = raop_playback_start ,
. playback_stop = raop_playback_stop ,
. write = raop_v2_write ,
. flush = raop_flush ,
. status_cb = raop_set_status_cb ,
. metadata_prepare = raop_metadata_prepare ,
. metadata_send = raop_metadata_send ,
. metadata_purge = raop_metadata_purge ,
. metadata_prune = raop_metadata_prune ,
2017-06-19 15:52:01 -04:00
# ifdef RAOP_VERIFICATION
. authorize = raop_verification_setup ,
# endif
2016-01-23 19:14:07 -05:00
} ;