s/sout_stream_id_t/sout_stream_id_sys_t/
[vlc.git] / modules / stream_out / raop.c
blobd07c57f5c4c1036449d5ffd4c26eefc8c4341343
1 /*****************************************************************************
2 * raop.c: Remote Audio Output Protocol streaming support
3 *****************************************************************************
4 * Copyright (C) 2008 VLC authors and VideoLAN
5 * $Id$
7 * Author: Michael Hanselmann
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
25 * Preamble
26 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include <assert.h>
33 #include <gcrypt.h>
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_sout.h>
38 #include <vlc_block.h>
39 #include <vlc_network.h>
40 #include <vlc_strings.h>
41 #include <vlc_charset.h>
42 #include <vlc_fs.h>
43 #include <vlc_gcrypt.h>
44 #include <vlc_es.h>
45 #include <vlc_http.h>
46 #include <vlc_memory.h>
48 #define RAOP_PORT 5000
49 #define RAOP_USER_AGENT "VLC " VERSION
52 static const char ps_raop_rsa_pubkey[] =
53 "\xe7\xd7\x44\xf2\xa2\xe2\x78\x8b\x6c\x1f\x55\xa0\x8e\xb7\x05\x44"
54 "\xa8\xfa\x79\x45\xaa\x8b\xe6\xc6\x2c\xe5\xf5\x1c\xbd\xd4\xdc\x68"
55 "\x42\xfe\x3d\x10\x83\xdd\x2e\xde\xc1\xbf\xd4\x25\x2d\xc0\x2e\x6f"
56 "\x39\x8b\xdf\x0e\x61\x48\xea\x84\x85\x5e\x2e\x44\x2d\xa6\xd6\x26"
57 "\x64\xf6\x74\xa1\xf3\x04\x92\x9a\xde\x4f\x68\x93\xef\x2d\xf6\xe7"
58 "\x11\xa8\xc7\x7a\x0d\x91\xc9\xd9\x80\x82\x2e\x50\xd1\x29\x22\xaf"
59 "\xea\x40\xea\x9f\x0e\x14\xc0\xf7\x69\x38\xc5\xf3\x88\x2f\xc0\x32"
60 "\x3d\xd9\xfe\x55\x15\x5f\x51\xbb\x59\x21\xc2\x01\x62\x9f\xd7\x33"
61 "\x52\xd5\xe2\xef\xaa\xbf\x9b\xa0\x48\xd7\xb8\x13\xa2\xb6\x76\x7f"
62 "\x6c\x3c\xcf\x1e\xb4\xce\x67\x3d\x03\x7b\x0d\x2e\xa3\x0c\x5f\xff"
63 "\xeb\x06\xf8\xd0\x8a\xdd\xe4\x09\x57\x1a\x9c\x68\x9f\xef\x10\x72"
64 "\x88\x55\xdd\x8c\xfb\x9a\x8b\xef\x5c\x89\x43\xef\x3b\x5f\xaa\x15"
65 "\xdd\xe6\x98\xbe\xdd\xf3\x59\x96\x03\xeb\x3e\x6f\x61\x37\x2b\xb6"
66 "\x28\xf6\x55\x9f\x59\x9a\x78\xbf\x50\x06\x87\xaa\x7f\x49\x76\xc0"
67 "\x56\x2d\x41\x29\x56\xf8\x98\x9e\x18\xa6\x35\x5b\xd8\x15\x97\x82"
68 "\x5e\x0f\xc8\x75\x34\x3e\xc7\x82\x11\x76\x25\xcd\xbf\x98\x44\x7b";
70 static const char ps_raop_rsa_exp[] = "\x01\x00\x01";
72 static const char psz_delim_space[] = " ";
73 static const char psz_delim_colon[] = ":";
74 static const char psz_delim_equal[] = "=";
75 static const char psz_delim_semicolon[] = ";";
78 /*****************************************************************************
79 * Prototypes
80 *****************************************************************************/
81 static int Open( vlc_object_t * );
82 static void Close( vlc_object_t * );
84 static sout_stream_id_sys_t *Add( sout_stream_t *, es_format_t * );
85 static int Del( sout_stream_t *, sout_stream_id_sys_t * );
86 static int Send( sout_stream_t *, sout_stream_id_sys_t *, block_t* );
88 static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
89 vlc_value_t oldval, vlc_value_t newval,
90 void *p_data );
92 typedef enum
94 JACK_TYPE_NONE = 0,
95 JACK_TYPE_ANALOG,
96 JACK_TYPE_DIGITAL,
97 } jack_type_t;
99 struct sout_stream_sys_t
101 /* Input parameters */
102 char *psz_host;
103 char *psz_password;
104 int i_volume;
106 /* Plugin status */
107 sout_stream_id_sys_t *p_audio_stream;
108 bool b_alac_warning;
109 bool b_volume_callback;
111 /* Connection state */
112 int i_control_fd;
113 int i_stream_fd;
115 uint8_t ps_aes_key[16];
116 uint8_t ps_aes_iv[16];
117 gcry_cipher_hd_t aes_ctx;
119 char *psz_url;
120 char *psz_client_instance;
121 char *psz_session;
122 char *psz_last_status_line;
124 int i_cseq;
125 int i_server_port;
126 int i_audio_latency;
127 int i_jack_type;
129 http_auth_t auth;
131 /* Send buffer */
132 size_t i_sendbuf_len;
133 uint8_t *p_sendbuf;
136 struct sout_stream_id_sys_t
138 es_format_t fmt;
142 /*****************************************************************************
143 * Module descriptor
144 *****************************************************************************/
145 #define SOUT_CFG_PREFIX "sout-raop-"
147 #define HOST_TEXT N_("Host")
148 #define HOST_LONGTEXT N_("Hostname or IP address of target device")
150 #define VOLUME_TEXT N_("Volume")
151 #define VOLUME_LONGTEXT N_("Output volume for analog output: 0 for silence, " \
152 "1..255 from almost silent to very loud.")
154 #define PASSWORD_TEXT N_("Password")
155 #define PASSWORD_LONGTEXT N_("Password for target device.")
157 #define PASSWORD_FILE_TEXT N_("Password file")
158 #define PASSWORD_FILE_LONGTEXT N_("Read password for target device from file.")
160 vlc_module_begin();
161 set_shortname( N_("RAOP") )
162 set_description( N_("Remote Audio Output Protocol stream output") )
163 set_capability( "sout stream", 0 )
164 add_shortcut( "raop" )
165 set_category( CAT_SOUT )
166 set_subcategory( SUBCAT_SOUT_STREAM )
167 add_string( SOUT_CFG_PREFIX "host", "",
168 HOST_TEXT, HOST_LONGTEXT, false )
169 add_password( SOUT_CFG_PREFIX "password", NULL,
170 PASSWORD_TEXT, PASSWORD_LONGTEXT, false )
171 add_loadfile( SOUT_CFG_PREFIX "password-file", NULL,
172 PASSWORD_FILE_TEXT, PASSWORD_FILE_LONGTEXT, false )
173 add_integer_with_range( SOUT_CFG_PREFIX "volume", 100, 0, 255,
174 VOLUME_TEXT, VOLUME_LONGTEXT, false )
175 set_callbacks( Open, Close )
176 vlc_module_end()
178 static const char *const ppsz_sout_options[] = {
179 "host",
180 "password",
181 "password-file",
182 "volume",
183 NULL
187 /*****************************************************************************
188 * Utilities:
189 *****************************************************************************/
190 static void FreeSys( vlc_object_t *p_this, sout_stream_sys_t *p_sys )
192 sout_stream_t *p_stream = (sout_stream_t*)p_this;
194 if ( p_sys->i_control_fd >= 0 )
195 net_Close( p_sys->i_control_fd );
196 if ( p_sys->i_stream_fd >= 0 )
197 net_Close( p_sys->i_stream_fd );
198 if ( p_sys->b_volume_callback )
199 var_DelCallback( p_stream, SOUT_CFG_PREFIX "volume",
200 VolumeCallback, NULL );
202 gcry_cipher_close( p_sys->aes_ctx );
204 free( p_sys->p_sendbuf );
205 free( p_sys->psz_host );
206 free( p_sys->psz_password );
207 free( p_sys->psz_url );
208 free( p_sys->psz_session );
209 free( p_sys->psz_client_instance );
210 free( p_sys->psz_last_status_line );
211 free( p_sys );
214 static void FreeId( sout_stream_id_sys_t *id )
216 free( id );
219 static void RemoveBase64Padding( char *str )
221 char *ps_pos = strchr( str, '=' );
222 if ( ps_pos != NULL )
223 *ps_pos = '\0';
226 static int CheckForGcryptErrorWithLine( sout_stream_t *p_stream,
227 gcry_error_t i_gcrypt_err,
228 unsigned int i_line )
230 if ( i_gcrypt_err != GPG_ERR_NO_ERROR )
232 msg_Err( p_stream, "gcrypt error (line %d): %s", i_line,
233 gpg_strerror( i_gcrypt_err ) );
234 return 1;
237 return 0;
240 /* Wrapper to pass line number for easier debugging */
241 #define CheckForGcryptError( p_this, i_gcrypt_err ) \
242 CheckForGcryptErrorWithLine( p_this, i_gcrypt_err, __LINE__ )
244 /* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
245 * specification.
247 static int MGF1( vlc_object_t *p_this,
248 unsigned char *mask, size_t l,
249 const unsigned char *Z, const size_t zLen,
250 const int Hash )
252 sout_stream_t *p_stream = (sout_stream_t*)p_this;
253 gcry_error_t i_gcrypt_err;
254 gcry_md_hd_t md_handle = NULL;
255 unsigned int hLen;
256 unsigned char *ps_md;
257 uint32_t counter = 0;
258 uint8_t C[4];
259 size_t i_copylen;
260 int i_err = VLC_SUCCESS;
262 assert( mask != NULL );
263 assert( Z != NULL );
265 hLen = gcry_md_get_algo_dlen( Hash );
267 i_gcrypt_err = gcry_md_open( &md_handle, Hash, 0 );
268 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
270 i_err = VLC_EGENERIC;
271 goto error;
274 while ( l > 0 )
276 /* 3. For counter from 0 to \lceil{l / hLen}\rceil-1, do the following:
277 * a. Convert counter to an octet string C of length 4 with the
278 * primitive I2OSP: C = I2OSP (counter, 4)
280 C[0] = (counter >> 24) & 0xff;
281 C[1] = (counter >> 16) & 0xff;
282 C[2] = (counter >> 8) & 0xff;
283 C[3] = counter & 0xff;
284 ++counter;
286 /* b. Concatenate the hash of the seed Z and C to the octet string T:
287 * T = T || Hash (Z || C)
289 gcry_md_reset( md_handle );
290 gcry_md_write( md_handle, Z, zLen );
291 gcry_md_write( md_handle, C, 4 );
292 ps_md = gcry_md_read( md_handle, Hash );
294 /* 4. Output the leading l octets of T as the octet string mask. */
295 i_copylen = __MIN( l, hLen );
296 memcpy( mask, ps_md, i_copylen );
297 mask += i_copylen;
298 l -= i_copylen;
301 error:
302 gcry_md_close( md_handle );
304 return i_err;
307 /* EME-OAEP-ENCODE is specified in RFC2437, section 9.1.1.1. Variables are
308 * named after the specification.
310 static int AddOaepPadding( vlc_object_t *p_this,
311 unsigned char *EM, const size_t emLenWithPrefix,
312 const unsigned char *M, const size_t mLen,
313 const unsigned char *P, const size_t pLen )
315 const int Hash = GCRY_MD_SHA1;
316 const unsigned int hLen = gcry_md_get_algo_dlen( Hash );
317 unsigned char *seed = NULL;
318 unsigned char *DB = NULL;
319 unsigned char *dbMask = NULL;
320 unsigned char *seedMask = NULL;
321 size_t emLen;
322 size_t psLen;
323 size_t i;
324 int i_err = VLC_SUCCESS;
326 /* Space for 0x00 prefix in EM. */
327 emLen = emLenWithPrefix - 1;
329 /* Step 2:
330 * If ||M|| > emLen-2hLen-1 then output "message too long" and stop.
332 if ( mLen > (emLen - (2 * hLen) - 1) )
334 msg_Err( p_this , "Message too long" );
335 goto error;
338 /* Step 3:
339 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
340 * octets. The length of PS may be 0.
342 psLen = emLen - mLen - (2 * hLen) - 1;
345 * Step 5:
346 * Concatenate pHash, PS, the message M, and other padding to form a data
347 * block DB as: DB = pHash || PS || 01 || M
349 DB = calloc( 1, hLen + psLen + 1 + mLen );
350 dbMask = calloc( 1, emLen - hLen );
351 seedMask = calloc( 1, hLen );
353 if ( DB == NULL || dbMask == NULL || seedMask == NULL )
355 i_err = VLC_ENOMEM;
356 goto error;
359 /* Step 4:
360 * Let pHash = Hash(P), an octet string of length hLen.
362 gcry_md_hash_buffer( Hash, DB, P, pLen );
364 /* Step 3:
365 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
366 * octets. The length of PS may be 0.
368 memset( DB + hLen, 0, psLen );
370 /* Step 5:
371 * Concatenate pHash, PS, the message M, and other padding to form a data
372 * block DB as: DB = pHash || PS || 01 || M
374 DB[hLen + psLen] = 0x01;
375 memcpy( DB + hLen + psLen + 1, M, mLen );
377 /* Step 6:
378 * Generate a random octet string seed of length hLen
380 seed = gcry_random_bytes( hLen, GCRY_STRONG_RANDOM );
381 if ( seed == NULL )
383 i_err = VLC_ENOMEM;
384 goto error;
387 /* Step 7:
388 * Let dbMask = MGF(seed, emLen-hLen).
390 i_err = MGF1( p_this, dbMask, emLen - hLen, seed, hLen, Hash );
391 if ( i_err != VLC_SUCCESS )
392 goto error;
394 /* Step 8:
395 * Let maskedDB = DB \xor dbMask.
397 for ( i = 0; i < (emLen - hLen); ++i )
398 DB[i] ^= dbMask[i];
400 /* Step 9:
401 * Let seedMask = MGF(maskedDB, hLen).
403 i_err = MGF1( p_this, seedMask, hLen, DB, emLen - hLen, Hash );
404 if ( i_err != VLC_SUCCESS )
405 goto error;
407 /* Step 10:
408 * Let maskedSeed = seed \xor seedMask.
410 for ( i = 0; i < hLen; ++i )
411 seed[i] ^= seedMask[i];
413 /* Step 11:
414 * Let EM = maskedSeed || maskedDB.
416 assert( (1 + hLen + (hLen + psLen + 1 + mLen)) == emLenWithPrefix );
417 EM[0] = 0x00;
418 memcpy( EM + 1, seed, hLen );
419 memcpy( EM + 1 + hLen, DB, hLen + psLen + 1 + mLen );
421 /* Step 12:
422 * Output EM.
425 error:
426 free( DB );
427 free( dbMask );
428 free( seedMask );
429 free( seed );
431 return i_err;
434 static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result )
436 sout_stream_t *p_stream = (sout_stream_t*)p_this;
437 sout_stream_sys_t *p_sys = p_stream->p_sys;
438 gcry_error_t i_gcrypt_err;
439 gcry_sexp_t sexp_rsa_params = NULL;
440 gcry_sexp_t sexp_input = NULL;
441 gcry_sexp_t sexp_encrypted = NULL;
442 gcry_sexp_t sexp_token_a = NULL;
443 gcry_mpi_t mpi_pubkey = NULL;
444 gcry_mpi_t mpi_exp = NULL;
445 gcry_mpi_t mpi_input = NULL;
446 gcry_mpi_t mpi_output = NULL;
447 unsigned char ps_padded_key[256];
448 unsigned char *ps_value;
449 size_t i_value_size;
450 int i_err;
452 /* Add RSA-OAES-SHA1 padding */
453 i_err = AddOaepPadding( p_this,
454 ps_padded_key, sizeof( ps_padded_key ),
455 p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
456 NULL, 0 );
457 if ( i_err != VLC_SUCCESS )
458 goto error;
459 i_err = VLC_EGENERIC;
461 /* Read public key */
462 i_gcrypt_err = gcry_mpi_scan( &mpi_pubkey, GCRYMPI_FMT_USG,
463 ps_raop_rsa_pubkey,
464 sizeof( ps_raop_rsa_pubkey ) - 1, NULL );
465 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
466 goto error;
468 /* Read exponent */
469 i_gcrypt_err = gcry_mpi_scan( &mpi_exp, GCRYMPI_FMT_USG, ps_raop_rsa_exp,
470 sizeof( ps_raop_rsa_exp ) - 1, NULL );
471 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
472 goto error;
474 /* If the input data starts with a set bit (0x80), gcrypt thinks it's a
475 * signed integer and complains. Prefixing it with a zero byte (\0)
476 * works, but involves more work. Converting it to an MPI in our code is
477 * cleaner.
479 i_gcrypt_err = gcry_mpi_scan( &mpi_input, GCRYMPI_FMT_USG,
480 ps_padded_key, sizeof( ps_padded_key ),
481 NULL);
482 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
483 goto error;
485 /* Build S-expression with RSA parameters */
486 i_gcrypt_err = gcry_sexp_build( &sexp_rsa_params, NULL,
487 "(public-key(rsa(n %m)(e %m)))",
488 mpi_pubkey, mpi_exp );
489 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
490 goto error;
492 /* Build S-expression for data */
493 i_gcrypt_err = gcry_sexp_build( &sexp_input, NULL, "(data(value %m))",
494 mpi_input );
495 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
496 goto error;
498 /* Encrypt data */
499 i_gcrypt_err = gcry_pk_encrypt( &sexp_encrypted, sexp_input,
500 sexp_rsa_params );
501 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
502 goto error;
504 /* Extract encrypted data */
505 sexp_token_a = gcry_sexp_find_token( sexp_encrypted, "a", 0 );
506 if ( !sexp_token_a )
508 msg_Err( p_this , "Token 'a' not found in result S-expression" );
509 goto error;
512 mpi_output = gcry_sexp_nth_mpi( sexp_token_a, 1, GCRYMPI_FMT_USG );
513 if ( !mpi_output )
515 msg_Err( p_this, "Unable to extract MPI from result" );
516 goto error;
519 /* Copy encrypted data into char array */
520 i_gcrypt_err = gcry_mpi_aprint( GCRYMPI_FMT_USG, &ps_value, &i_value_size,
521 mpi_output );
522 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
524 goto error;
527 /* Encode in Base64 */
528 *result = vlc_b64_encode_binary( ps_value, i_value_size );
529 i_err = VLC_SUCCESS;
531 error:
532 gcry_sexp_release( sexp_rsa_params );
533 gcry_sexp_release( sexp_input );
534 gcry_sexp_release( sexp_encrypted );
535 gcry_sexp_release( sexp_token_a );
536 gcry_mpi_release( mpi_pubkey );
537 gcry_mpi_release( mpi_exp );
538 gcry_mpi_release( mpi_input );
539 gcry_mpi_release( mpi_output );
541 return i_err;
544 static char *ReadPasswordFile( vlc_object_t *p_this, const char *psz_path )
546 FILE *p_file = NULL;
547 char *psz_password = NULL;
548 char *psz_newline;
549 char ps_buffer[256];
551 p_file = vlc_fopen( psz_path, "rt" );
552 if ( p_file == NULL )
554 msg_Err( p_this, "Unable to open password file '%s': %s", psz_path,
555 vlc_strerror_c(errno) );
556 goto error;
559 /* Read one line only */
560 if ( fgets( ps_buffer, sizeof( ps_buffer ), p_file ) == NULL )
562 if ( ferror( p_file ) )
564 msg_Err( p_this, "Error reading '%s': %s", psz_path,
565 vlc_strerror_c(errno) );
566 goto error;
569 /* Nothing was read, but there was no error either. Maybe the file is
570 * empty. Not all implementations of fgets(3) write \0 to the output
571 * buffer in this case.
573 ps_buffer[0] = '\0';
575 } else {
576 /* Replace first newline with '\0' */
577 psz_newline = strchr( ps_buffer, '\n' );
578 if ( psz_newline != NULL )
579 *psz_newline = '\0';
582 if ( *ps_buffer == '\0' ) {
583 msg_Err( p_this, "No password could be read from '%s'", psz_path );
584 goto error;
587 psz_password = strdup( ps_buffer );
589 error:
590 if ( p_file != NULL )
591 fclose( p_file );
593 return psz_password;
596 /* Splits the value of a received header.
598 * Example: "Transport: RTP/AVP/TCP;unicast;mode=record;server_port=6000"
600 static int SplitHeader( char **ppsz_next, char **ppsz_name,
601 char **ppsz_value )
603 /* Find semicolon (separator between assignments) */
604 *ppsz_name = strsep( ppsz_next, psz_delim_semicolon );
605 if ( *ppsz_name )
607 /* Skip spaces */
608 *ppsz_name += strspn( *ppsz_name, psz_delim_space );
610 /* Get value */
611 *ppsz_value = *ppsz_name;
612 strsep( ppsz_value, psz_delim_equal );
614 else
615 *ppsz_value = NULL;
617 return !!*ppsz_name;
620 static void FreeHeader( void *p_value, void *p_data )
622 VLC_UNUSED( p_data );
623 free( p_value );
626 static int ReadStatusLine( vlc_object_t *p_this )
628 sout_stream_t *p_stream = (sout_stream_t*)p_this;
629 sout_stream_sys_t *p_sys = p_stream->p_sys;
630 char *psz_line = NULL;
631 char *psz_token;
632 char *psz_next;
633 int i_result = VLC_EGENERIC;
635 p_sys->psz_last_status_line = net_Gets( p_this, p_sys->i_control_fd,
636 NULL );
637 if ( !p_sys->psz_last_status_line )
638 goto error;
640 /* Create working copy */
641 psz_line = strdup( p_sys->psz_last_status_line );
642 psz_next = psz_line;
644 /* Protocol field */
645 psz_token = strsep( &psz_next, psz_delim_space );
646 if ( !psz_token || strncmp( psz_token, "RTSP/1.", 7 ) != 0 )
648 msg_Err( p_this, "Unknown protocol (%s)",
649 p_sys->psz_last_status_line );
650 goto error;
653 /* Status field */
654 psz_token = strsep( &psz_next, psz_delim_space );
655 if ( !psz_token )
657 msg_Err( p_this, "Request failed (%s)",
658 p_sys->psz_last_status_line );
659 goto error;
662 i_result = atoi( psz_token );
664 error:
665 free( psz_line );
667 return i_result;
670 static int ReadHeader( vlc_object_t *p_this,
671 vlc_dictionary_t *p_resp_headers,
672 int *done )
674 sout_stream_t *p_stream = (sout_stream_t*)p_this;
675 sout_stream_sys_t *p_sys = p_stream->p_sys;
676 char *psz_original = NULL;
677 char *psz_line = NULL;
678 char *psz_token;
679 char *psz_next;
680 char *psz_name;
681 char *psz_value;
682 int i_err = VLC_SUCCESS;
684 psz_line = net_Gets( p_this, p_sys->i_control_fd, NULL );
685 if ( !psz_line )
687 i_err = VLC_EGENERIC;
688 goto error;
691 /* Empty line for response end */
692 if ( psz_line[0] == '\0' )
693 *done = 1;
694 else
696 psz_original = strdup( psz_line );
697 psz_next = psz_line;
699 psz_token = strsep( &psz_next, psz_delim_colon );
700 if ( !psz_token || psz_next[0] != ' ' )
702 msg_Err( p_this, "Invalid header format (%s)", psz_original );
703 i_err = VLC_EGENERIC;
704 goto error;
707 psz_name = psz_token;
708 psz_value = psz_next + 1;
710 vlc_dictionary_insert( p_resp_headers, psz_name, strdup( psz_value ) );
713 error:
714 free( psz_original );
715 free( psz_line );
717 return i_err;
720 static int WriteAuxHeaders( vlc_object_t *p_this,
721 vlc_dictionary_t *p_req_headers )
723 sout_stream_t *p_stream = (sout_stream_t*)p_this;
724 sout_stream_sys_t *p_sys = p_stream->p_sys;
725 char **ppsz_keys = NULL;
726 char *psz_key;
727 char *psz_value;
728 int i_err = VLC_SUCCESS;
729 int i_rc;
730 size_t i;
732 ppsz_keys = vlc_dictionary_all_keys( p_req_headers );
733 for ( i = 0; ppsz_keys[i]; ++i )
735 psz_key = ppsz_keys[i];
736 psz_value = vlc_dictionary_value_for_key( p_req_headers, psz_key );
738 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
739 "%s: %s\r\n", psz_key, psz_value );
740 if ( i_rc < 0 )
742 i_err = VLC_EGENERIC;
743 goto error;
747 error:
748 for ( i = 0; ppsz_keys[i]; ++i )
749 free( ppsz_keys[i] );
750 free( ppsz_keys );
752 return i_err;
755 static int SendRequest( vlc_object_t *p_this, const char *psz_method,
756 const char *psz_content_type, const char *psz_body,
757 vlc_dictionary_t *p_req_headers )
759 sout_stream_t *p_stream = (sout_stream_t*)p_this;
760 sout_stream_sys_t *p_sys = p_stream->p_sys;
761 const unsigned char psz_headers_end[] = "\r\n";
762 size_t i_body_length = 0;
763 int i_err = VLC_SUCCESS;
764 int i_rc;
766 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
767 "%s %s RTSP/1.0\r\n"
768 "User-Agent: " RAOP_USER_AGENT "\r\n"
769 "Client-Instance: %s\r\n"
770 "CSeq: %d\r\n",
771 psz_method, p_sys->psz_url,
772 p_sys->psz_client_instance,
773 ++p_sys->i_cseq );
774 if ( i_rc < 0 )
776 i_err = VLC_EGENERIC;
777 goto error;
780 if ( psz_content_type )
782 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
783 "Content-Type: %s\r\n", psz_content_type );
784 if ( i_rc < 0 )
786 i_err = VLC_ENOMEM;
787 goto error;
791 if ( psz_body )
793 i_body_length = strlen( psz_body );
795 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
796 "Content-Length: %u\r\n",
797 (unsigned int)i_body_length );
798 if ( i_rc < 0 )
800 i_err = VLC_ENOMEM;
801 goto error;
805 i_err = WriteAuxHeaders( p_this, p_req_headers );
806 if ( i_err != VLC_SUCCESS )
807 goto error;
809 i_rc = net_Write( p_this, p_sys->i_control_fd, NULL,
810 psz_headers_end, sizeof( psz_headers_end ) - 1 );
811 if ( i_rc < 0 )
813 i_err = VLC_ENOMEM;
814 goto error;
817 if ( psz_body )
818 net_Write( p_this, p_sys->i_control_fd, NULL,
819 psz_body, i_body_length );
821 error:
822 return i_err;
825 static int ParseAuthenticateHeader( vlc_object_t *p_this,
826 vlc_dictionary_t *p_resp_headers )
828 sout_stream_t *p_stream = (sout_stream_t*)p_this;
829 sout_stream_sys_t *p_sys = p_stream->p_sys;
830 char *psz_auth;
831 int i_err = VLC_SUCCESS;
833 psz_auth = vlc_dictionary_value_for_key( p_resp_headers,
834 "WWW-Authenticate" );
835 if ( psz_auth == NULL )
837 msg_Err( p_this, "HTTP 401 response missing "
838 "WWW-Authenticate header" );
839 i_err = VLC_EGENERIC;
840 goto error;
843 http_auth_ParseWwwAuthenticateHeader( p_this, &p_sys->auth, psz_auth );
845 error:
846 return i_err;
849 static int ExecRequest( vlc_object_t *p_this, const char *psz_method,
850 const char *psz_content_type, const char *psz_body,
851 vlc_dictionary_t *p_req_headers,
852 vlc_dictionary_t *p_resp_headers )
854 sout_stream_t *p_stream = (sout_stream_t*)p_this;
855 sout_stream_sys_t *p_sys = p_stream->p_sys;
856 char *psz_authorization = NULL;
857 int headers_done;
858 int i_err = VLC_SUCCESS;
859 int i_status;
860 int i_auth_state;
862 if ( p_sys->i_control_fd < 0 )
864 msg_Err( p_this, "Control connection not open" );
865 i_err = VLC_EGENERIC;
866 goto error;
869 i_auth_state = 0;
870 while ( 1 )
872 /* Send header only when Digest authentication is used */
873 if ( p_sys->psz_password != NULL && p_sys->auth.psz_nonce != NULL )
875 FREENULL( psz_authorization );
877 psz_authorization =
878 http_auth_FormatAuthorizationHeader( p_this, &p_sys->auth,
879 psz_method,
880 p_sys->psz_url, "",
881 p_sys->psz_password );
882 if ( psz_authorization == NULL )
884 i_err = VLC_EGENERIC;
885 goto error;
888 vlc_dictionary_insert( p_req_headers, "Authorization",
889 psz_authorization );
892 /* Send request */
893 i_err = SendRequest( p_this, psz_method, psz_content_type, psz_body,
894 p_req_headers);
895 if ( i_err != VLC_SUCCESS )
896 goto error;
898 /* Read status line */
899 i_status = ReadStatusLine( p_this );
900 if ( i_status < 0 )
902 i_err = i_status;
903 goto error;
906 vlc_dictionary_clear( p_resp_headers, FreeHeader, NULL );
908 /* Read headers */
909 headers_done = 0;
910 while ( !headers_done )
912 i_err = ReadHeader( p_this, p_resp_headers, &headers_done );
913 if ( i_err != VLC_SUCCESS )
914 goto error;
917 if ( i_status == 200 )
918 /* Request successful */
919 break;
920 else if ( i_status == 401 )
922 /* Authorization required */
923 if ( i_auth_state == 1 || p_sys->psz_password == NULL )
925 msg_Err( p_this, "Access denied, password invalid" );
926 i_err = VLC_EGENERIC;
927 goto error;
930 i_err = ParseAuthenticateHeader( p_this, p_resp_headers );
931 if ( i_err != VLC_SUCCESS )
932 goto error;
934 i_auth_state = 1;
936 else
938 msg_Err( p_this, "Request failed (%s), status is %d",
939 p_sys->psz_last_status_line, i_status );
940 i_err = VLC_EGENERIC;
941 goto error;
945 error:
946 FREENULL( p_sys->psz_last_status_line );
947 free( psz_authorization );
949 return i_err;
952 static int AnnounceSDP( vlc_object_t *p_this, char *psz_local,
953 uint32_t i_session_id )
955 sout_stream_t *p_stream = (sout_stream_t*)p_this;
956 sout_stream_sys_t *p_sys = p_stream->p_sys;
957 vlc_dictionary_t req_headers;
958 vlc_dictionary_t resp_headers;
959 unsigned char ps_sac[16];
960 char *psz_sdp = NULL;
961 char *psz_sac_base64 = NULL;
962 char *psz_aes_key_base64 = NULL;
963 char *psz_aes_iv_base64 = NULL;
964 int i_err = VLC_SUCCESS;
965 int i_rc;
967 vlc_dictionary_init( &req_headers, 0 );
968 vlc_dictionary_init( &resp_headers, 0 );
970 /* Encrypt AES key and encode it in Base64 */
971 i_rc = EncryptAesKeyBase64( p_this, &psz_aes_key_base64 );
972 if ( i_rc != VLC_SUCCESS || psz_aes_key_base64 == NULL )
974 i_err = VLC_EGENERIC;
975 goto error;
977 RemoveBase64Padding( psz_aes_key_base64 );
979 /* Encode AES IV in Base64 */
980 psz_aes_iv_base64 = vlc_b64_encode_binary( p_sys->ps_aes_iv,
981 sizeof( p_sys->ps_aes_iv ) );
982 if ( psz_aes_iv_base64 == NULL )
984 i_err = VLC_EGENERIC;
985 goto error;
987 RemoveBase64Padding( psz_aes_iv_base64 );
989 /* Random bytes for Apple-Challenge header */
990 gcry_randomize( ps_sac, sizeof( ps_sac ), GCRY_STRONG_RANDOM );
992 psz_sac_base64 = vlc_b64_encode_binary( ps_sac, sizeof( ps_sac ) );
993 if ( psz_sac_base64 == NULL )
995 i_err = VLC_EGENERIC;
996 goto error;
998 RemoveBase64Padding( psz_sac_base64 );
1000 /* Build SDP
1001 * Note: IPv6 addresses also use "IP4". Make sure not to include the
1002 * scope ID.
1004 i_rc = asprintf( &psz_sdp,
1005 "v=0\r\n"
1006 "o=iTunes %u 0 IN IP4 %s\r\n"
1007 "s=iTunes\r\n"
1008 "c=IN IP4 %s\r\n"
1009 "t=0 0\r\n"
1010 "m=audio 0 RTP/AVP 96\r\n"
1011 "a=rtpmap:96 AppleLossless\r\n"
1012 "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n"
1013 "a=rsaaeskey:%s\r\n"
1014 "a=aesiv:%s\r\n",
1015 i_session_id, psz_local, p_sys->psz_host,
1016 psz_aes_key_base64, psz_aes_iv_base64 );
1018 if ( i_rc < 0 )
1020 i_err = VLC_ENOMEM;
1021 goto error;
1024 /* Build and send request */
1025 vlc_dictionary_insert( &req_headers, "Apple-Challenge", psz_sac_base64 );
1027 i_err = ExecRequest( p_this, "ANNOUNCE", "application/sdp", psz_sdp,
1028 &req_headers, &resp_headers);
1029 if ( i_err != VLC_SUCCESS )
1030 goto error;
1032 error:
1033 vlc_dictionary_clear( &req_headers, NULL, NULL );
1034 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1036 free( psz_sdp );
1037 free( psz_sac_base64 );
1038 free( psz_aes_key_base64 );
1039 free( psz_aes_iv_base64 );
1041 return i_err;
1044 static int SendSetup( vlc_object_t *p_this )
1046 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1047 sout_stream_sys_t *p_sys = p_stream->p_sys;
1048 vlc_dictionary_t req_headers;
1049 vlc_dictionary_t resp_headers;
1050 int i_err = VLC_SUCCESS;
1051 char *psz_tmp;
1052 char *psz_next;
1053 char *psz_name;
1054 char *psz_value;
1056 vlc_dictionary_init( &req_headers, 0 );
1057 vlc_dictionary_init( &resp_headers, 0 );
1059 vlc_dictionary_insert( &req_headers, "Transport",
1060 ((void*)"RTP/AVP/TCP;unicast;interleaved=0-1;"
1061 "mode=record") );
1063 i_err = ExecRequest( p_this, "SETUP", NULL, NULL,
1064 &req_headers, &resp_headers );
1065 if ( i_err != VLC_SUCCESS )
1066 goto error;
1068 psz_tmp = vlc_dictionary_value_for_key( &resp_headers, "Session" );
1069 if ( !psz_tmp )
1071 msg_Err( p_this, "Missing 'Session' header during setup" );
1072 i_err = VLC_EGENERIC;
1073 goto error;
1076 free( p_sys->psz_session );
1077 p_sys->psz_session = strdup( psz_tmp );
1079 /* Get server_port */
1080 psz_next = vlc_dictionary_value_for_key( &resp_headers, "Transport" );
1081 while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
1083 if ( psz_value && strcmp( psz_name, "server_port" ) == 0 )
1085 p_sys->i_server_port = atoi( psz_value );
1086 break;
1090 if ( !p_sys->i_server_port )
1092 msg_Err( p_this, "Missing 'server_port' during setup" );
1093 i_err = VLC_EGENERIC;
1094 goto error;
1097 /* Get jack type */
1098 psz_next = vlc_dictionary_value_for_key( &resp_headers,
1099 "Audio-Jack-Status" );
1100 while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
1102 if ( strcmp( psz_name, "type" ) != 0 )
1103 continue;
1105 if ( strcmp( psz_value, "analog" ) == 0 )
1106 p_sys->i_jack_type = JACK_TYPE_ANALOG;
1108 else if ( strcmp( psz_value, "digital" ) == 0 )
1109 p_sys->i_jack_type = JACK_TYPE_DIGITAL;
1111 break;
1114 error:
1115 vlc_dictionary_clear( &req_headers, NULL, NULL );
1116 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1118 return i_err;
1121 static int SendRecord( vlc_object_t *p_this )
1123 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1124 sout_stream_sys_t *p_sys = p_stream->p_sys;
1125 vlc_dictionary_t req_headers;
1126 vlc_dictionary_t resp_headers;
1127 int i_err = VLC_SUCCESS;
1128 char *psz_value;
1130 vlc_dictionary_init( &req_headers, 0 );
1131 vlc_dictionary_init( &resp_headers, 0 );
1133 vlc_dictionary_insert( &req_headers, "Range", (void *)"npt=0-" );
1134 vlc_dictionary_insert( &req_headers, "RTP-Info",
1135 (void *)"seq=0;rtptime=0" );
1136 vlc_dictionary_insert( &req_headers, "Session",
1137 (void *)p_sys->psz_session );
1139 i_err = ExecRequest( p_this, "RECORD", NULL, NULL,
1140 &req_headers, &resp_headers );
1141 if ( i_err != VLC_SUCCESS )
1142 goto error;
1144 psz_value = vlc_dictionary_value_for_key( &resp_headers, "Audio-Latency" );
1145 if ( psz_value )
1146 p_sys->i_audio_latency = atoi( psz_value );
1147 else
1148 p_sys->i_audio_latency = 0;
1150 error:
1151 vlc_dictionary_clear( &req_headers, NULL, NULL );
1152 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1154 return i_err;
1157 static int SendFlush( vlc_object_t *p_this )
1159 VLC_UNUSED( p_this );
1160 vlc_dictionary_t resp_headers;
1161 vlc_dictionary_t req_headers;
1162 int i_err = VLC_SUCCESS;
1164 vlc_dictionary_init( &req_headers, 0 );
1165 vlc_dictionary_init( &resp_headers, 0 );
1167 vlc_dictionary_insert( &req_headers, "RTP-Info",
1168 (void *)"seq=0;rtptime=0" );
1170 i_err = ExecRequest( p_this, "FLUSH", NULL, NULL,
1171 &req_headers, &resp_headers );
1172 if ( i_err != VLC_SUCCESS )
1173 goto error;
1175 error:
1176 vlc_dictionary_clear( &req_headers, NULL, NULL );
1177 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1179 return i_err;
1182 static int SendTeardown( vlc_object_t *p_this )
1184 vlc_dictionary_t resp_headers;
1185 vlc_dictionary_t req_headers;
1186 int i_err = VLC_SUCCESS;
1188 vlc_dictionary_init( &req_headers, 0 );
1189 vlc_dictionary_init( &resp_headers, 0 );
1191 i_err = ExecRequest( p_this, "TEARDOWN", NULL, NULL,
1192 &req_headers, &resp_headers );
1193 if ( i_err != VLC_SUCCESS )
1194 goto error;
1196 error:
1197 vlc_dictionary_clear( &req_headers, NULL, NULL );
1198 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1200 return i_err;
1203 static int UpdateVolume( vlc_object_t *p_this )
1205 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1206 sout_stream_sys_t *p_sys = p_stream->p_sys;
1207 vlc_dictionary_t req_headers;
1208 vlc_dictionary_t resp_headers;
1209 char *psz_parameters = NULL;
1210 double d_volume;
1211 int i_err = VLC_SUCCESS;
1212 int i_rc;
1214 vlc_dictionary_init( &req_headers, 0 );
1215 vlc_dictionary_init( &resp_headers, 0 );
1217 /* Our volume is 0..255, RAOP is -144..0 (-144 off, -30..0 on) */
1219 /* Limit range */
1220 p_sys->i_volume = VLC_CLIP( p_sys->i_volume, 0, 255 );
1222 if ( p_sys->i_volume == 0 )
1223 d_volume = -144.0;
1224 else
1225 d_volume = -30 + ( ( (double)p_sys->i_volume ) * 30.0 / 255.0 );
1227 /* Format without using locales */
1228 i_rc = us_asprintf( &psz_parameters, "volume: %0.6f\r\n", d_volume );
1229 if ( i_rc < 0 )
1231 i_err = VLC_ENOMEM;
1232 goto error;
1235 vlc_dictionary_insert( &req_headers, "Session",
1236 (void *)p_sys->psz_session );
1238 i_err = ExecRequest( p_this, "SET_PARAMETER",
1239 "text/parameters", psz_parameters,
1240 &req_headers, &resp_headers );
1241 if ( i_err != VLC_SUCCESS )
1242 goto error;
1244 error:
1245 vlc_dictionary_clear( &req_headers, NULL, NULL );
1246 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1247 free( psz_parameters );
1249 return i_err;
1252 static void LogInfo( vlc_object_t *p_this )
1254 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1255 sout_stream_sys_t *p_sys = p_stream->p_sys;
1256 const char *psz_jack_name;
1258 msg_Info( p_this, "Audio latency: %d", p_sys->i_audio_latency );
1260 switch ( p_sys->i_jack_type )
1262 case JACK_TYPE_ANALOG:
1263 psz_jack_name = "analog";
1264 break;
1266 case JACK_TYPE_DIGITAL:
1267 psz_jack_name = "digital";
1268 break;
1270 case JACK_TYPE_NONE:
1271 default:
1272 psz_jack_name = "none";
1273 break;
1276 msg_Info( p_this, "Jack type: %s", psz_jack_name );
1279 static void SendAudio( sout_stream_t *p_stream, block_t *p_buffer )
1281 sout_stream_sys_t *p_sys = p_stream->p_sys;
1282 gcry_error_t i_gcrypt_err;
1283 block_t *p_next;
1284 size_t i_len;
1285 size_t i_payload_len;
1286 size_t i_realloc_len;
1287 int rc;
1289 const uint8_t header[16] = {
1290 0x24, 0x00, 0x00, 0x00,
1291 0xf0, 0xff, 0x00, 0x00,
1292 0x00, 0x00, 0x00, 0x00,
1293 0x00, 0x00, 0x00, 0x00,
1296 while ( p_buffer )
1298 i_len = sizeof( header ) + p_buffer->i_buffer;
1300 /* Buffer resize needed? */
1301 if ( i_len > p_sys->i_sendbuf_len || p_sys->p_sendbuf == NULL )
1303 /* Grow in blocks of 4K */
1304 i_realloc_len = (1 + (i_len / 4096)) * 4096;
1306 p_sys->p_sendbuf = realloc_or_free( p_sys->p_sendbuf, i_realloc_len );
1307 if ( p_sys->p_sendbuf == NULL )
1308 goto error;
1310 p_sys->i_sendbuf_len = i_realloc_len;
1313 /* Fill buffer */
1314 memcpy( p_sys->p_sendbuf, header, sizeof( header ) );
1315 memcpy( p_sys->p_sendbuf + sizeof( header ),
1316 p_buffer->p_buffer, p_buffer->i_buffer );
1318 /* Calculate payload length and update header */
1319 i_payload_len = i_len - 4;
1320 if ( i_payload_len > 0xffff )
1322 msg_Err( p_stream, "Buffer is too long (%u bytes)",
1323 (unsigned int)i_payload_len );
1324 goto error;
1327 p_sys->p_sendbuf[2] = ( i_payload_len >> 8 ) & 0xff;
1328 p_sys->p_sendbuf[3] = i_payload_len & 0xff;
1330 /* Reset cipher */
1331 i_gcrypt_err = gcry_cipher_reset( p_sys->aes_ctx );
1332 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1333 goto error;
1335 /* Set IV */
1336 i_gcrypt_err = gcry_cipher_setiv( p_sys->aes_ctx, p_sys->ps_aes_iv,
1337 sizeof( p_sys->ps_aes_iv ) );
1338 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1339 goto error;
1341 /* Encrypt in place. Only full blocks of 16 bytes are encrypted,
1342 * the rest (0-15 bytes) is left unencrypted.
1344 i_gcrypt_err =
1345 gcry_cipher_encrypt( p_sys->aes_ctx,
1346 p_sys->p_sendbuf + sizeof( header ),
1347 ( p_buffer->i_buffer / 16 ) * 16,
1348 NULL, 0 );
1349 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1350 goto error;
1352 /* Send data */
1353 rc = net_Write( p_stream, p_sys->i_stream_fd, NULL,
1354 p_sys->p_sendbuf, i_len );
1355 if ( rc < 0 )
1356 goto error;
1358 p_next = p_buffer->p_next;
1359 block_Release( p_buffer );
1360 p_buffer = p_next;
1363 error:
1364 block_ChainRelease( p_buffer );
1365 return;
1369 /*****************************************************************************
1370 * Open:
1371 *****************************************************************************/
1372 static int Open( vlc_object_t *p_this )
1374 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1375 sout_stream_sys_t *p_sys;
1376 char psz_local[NI_MAXNUMERICHOST];
1377 char *psz_pwfile = NULL;
1378 gcry_error_t i_gcrypt_err;
1379 int i_err = VLC_SUCCESS;
1380 uint32_t i_session_id;
1381 uint64_t i_client_instance;
1383 vlc_gcrypt_init();
1385 config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
1386 p_stream->p_cfg );
1388 p_sys = calloc( 1, sizeof( *p_sys ) );
1389 if ( p_sys == NULL )
1391 i_err = VLC_ENOMEM;
1392 goto error;
1395 p_stream->pf_add = Add;
1396 p_stream->pf_del = Del;
1397 p_stream->pf_send = Send;
1398 p_stream->p_sys = p_sys;
1399 p_stream->pace_nocontrol = true;
1401 p_sys->i_control_fd = -1;
1402 p_sys->i_stream_fd = -1;
1403 p_sys->i_volume = var_GetInteger( p_stream, SOUT_CFG_PREFIX "volume");
1404 p_sys->i_jack_type = JACK_TYPE_NONE;
1406 http_auth_Init( &p_sys->auth );
1408 p_sys->psz_host = var_GetNonEmptyString( p_stream,
1409 SOUT_CFG_PREFIX "host" );
1410 if ( p_sys->psz_host == NULL )
1412 msg_Err( p_this, "Missing host" );
1413 i_err = VLC_EGENERIC;
1414 goto error;
1417 p_sys->psz_password = var_GetNonEmptyString( p_stream,
1418 SOUT_CFG_PREFIX "password" );
1419 if ( p_sys->psz_password == NULL )
1421 /* Try password file instead */
1422 psz_pwfile = var_GetNonEmptyString( p_stream,
1423 SOUT_CFG_PREFIX "password-file" );
1424 if ( psz_pwfile != NULL )
1426 p_sys->psz_password = ReadPasswordFile( p_this, psz_pwfile );
1427 if ( p_sys->psz_password == NULL )
1429 i_err = VLC_EGENERIC;
1430 goto error;
1435 if ( p_sys->psz_password != NULL )
1436 msg_Info( p_this, "Using password authentication" );
1438 var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume",
1439 VolumeCallback, NULL );
1440 p_sys->b_volume_callback = true;
1442 /* Open control connection */
1443 p_sys->i_control_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
1444 RAOP_PORT );
1445 if ( p_sys->i_control_fd < 0 )
1447 msg_Err( p_this, "Cannot establish control connection to %s:%d (%s)",
1448 p_sys->psz_host, RAOP_PORT, vlc_strerror_c(errno) );
1449 i_err = VLC_EGENERIC;
1450 goto error;
1453 /* Get local IP address */
1454 if ( net_GetSockAddress( p_sys->i_control_fd, psz_local, NULL ) )
1456 msg_Err( p_this, "cannot get local IP address" );
1457 i_err = VLC_EGENERIC;
1458 goto error;
1461 /* Random session ID */
1462 gcry_randomize( &i_session_id, sizeof( i_session_id ),
1463 GCRY_STRONG_RANDOM );
1465 /* Random client instance */
1466 gcry_randomize( &i_client_instance, sizeof( i_client_instance ),
1467 GCRY_STRONG_RANDOM );
1468 if ( asprintf( &p_sys->psz_client_instance, "%016"PRIX64,
1469 i_client_instance ) < 0 )
1471 i_err = VLC_ENOMEM;
1472 goto error;
1475 /* Build session URL */
1476 if ( asprintf( &p_sys->psz_url, "rtsp://%s/%u",
1477 psz_local, i_session_id ) < 0 )
1479 i_err = VLC_ENOMEM;
1480 goto error;
1483 /* Generate AES key and IV */
1484 gcry_randomize( p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
1485 GCRY_STRONG_RANDOM );
1486 gcry_randomize( p_sys->ps_aes_iv, sizeof( p_sys->ps_aes_iv ),
1487 GCRY_STRONG_RANDOM );
1489 /* Setup AES */
1490 i_gcrypt_err = gcry_cipher_open( &p_sys->aes_ctx, GCRY_CIPHER_AES,
1491 GCRY_CIPHER_MODE_CBC, 0 );
1492 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1494 i_err = VLC_EGENERIC;
1495 goto error;
1498 /* Set key */
1499 i_gcrypt_err = gcry_cipher_setkey( p_sys->aes_ctx, p_sys->ps_aes_key,
1500 sizeof( p_sys->ps_aes_key ) );
1501 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1503 i_err = VLC_EGENERIC;
1504 goto error;
1507 /* Protocol handshake */
1508 i_err = AnnounceSDP( p_this, psz_local, i_session_id );
1509 if ( i_err != VLC_SUCCESS )
1510 goto error;
1512 i_err = SendSetup( p_this );
1513 if ( i_err != VLC_SUCCESS )
1514 goto error;
1516 i_err = SendRecord( p_this );
1517 if ( i_err != VLC_SUCCESS )
1518 goto error;
1520 i_err = UpdateVolume( p_this );
1521 if ( i_err != VLC_SUCCESS )
1522 goto error;
1524 LogInfo( p_this );
1526 /* Open stream connection */
1527 p_sys->i_stream_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
1528 p_sys->i_server_port );
1529 if ( p_sys->i_stream_fd < 0 )
1531 msg_Err( p_this, "Cannot establish stream connection to %s:%d (%s)",
1532 p_sys->psz_host, p_sys->i_server_port,
1533 vlc_strerror_c(errno) );
1534 i_err = VLC_EGENERIC;
1535 goto error;
1538 error:
1539 free( psz_pwfile );
1541 if ( i_err != VLC_SUCCESS )
1542 FreeSys( p_this, p_sys );
1544 return i_err;
1548 /*****************************************************************************
1549 * Close:
1550 *****************************************************************************/
1551 static void Close( vlc_object_t *p_this )
1553 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1554 sout_stream_sys_t *p_sys = p_stream->p_sys;
1556 SendFlush( p_this );
1557 SendTeardown( p_this );
1559 FreeSys( p_this, p_sys );
1563 /*****************************************************************************
1564 * Add:
1565 *****************************************************************************/
1566 static sout_stream_id_sys_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
1568 sout_stream_sys_t *p_sys = p_stream->p_sys;
1569 sout_stream_id_sys_t *id = NULL;
1571 id = calloc( 1, sizeof( *id ) );
1572 if ( id == NULL )
1573 goto error;
1575 es_format_Copy( &id->fmt, p_fmt );
1577 switch ( id->fmt.i_cat )
1579 case AUDIO_ES:
1580 if ( id->fmt.i_codec == VLC_CODEC_ALAC )
1582 if ( p_sys->p_audio_stream )
1584 msg_Warn( p_stream, "Only the first Apple Lossless audio "
1585 "stream is used" );
1587 else if ( id->fmt.audio.i_rate != 44100 ||
1588 id->fmt.audio.i_channels != 2 )
1590 msg_Err( p_stream, "The Apple Lossless audio stream must be "
1591 "encoded with 44100 Hz and 2 channels" );
1593 else
1595 /* Use this stream */
1596 p_sys->p_audio_stream = id;
1599 else if ( !p_sys->b_alac_warning )
1601 msg_Err( p_stream, "Apple Lossless is the only codec supported. "
1602 "Use the \"transcode\" module for conversion "
1603 "(e.g. \"transcode{acodec=alac,"
1604 "channels=2}\")." );
1605 p_sys->b_alac_warning = true;
1608 break;
1610 default:
1611 /* Leave other stream types alone */
1612 break;
1615 return id;
1617 error:
1618 FreeId( id );
1620 return NULL;
1624 /*****************************************************************************
1625 * Del:
1626 *****************************************************************************/
1627 static int Del( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
1629 sout_stream_sys_t *p_sys = p_stream->p_sys;
1630 int i_err = VLC_SUCCESS;
1632 if ( p_sys->p_audio_stream == id )
1633 p_sys->p_audio_stream = NULL;
1635 FreeId( id );
1637 return i_err;
1641 /*****************************************************************************
1642 * Send:
1643 *****************************************************************************/
1644 static int Send( sout_stream_t *p_stream, sout_stream_id_sys_t *id,
1645 block_t *p_buffer )
1647 sout_stream_sys_t *p_sys = p_stream->p_sys;
1649 if ( id->fmt.i_cat == AUDIO_ES && id == p_sys->p_audio_stream )
1651 /* SendAudio takes care of releasing the buffers */
1652 SendAudio( p_stream, p_buffer );
1654 else
1656 block_ChainRelease( p_buffer );
1659 return VLC_SUCCESS;
1663 /*****************************************************************************
1664 * VolumeCallback: called when the volume is changed on the fly.
1665 *****************************************************************************/
1666 static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
1667 vlc_value_t oldval, vlc_value_t newval,
1668 void *p_data )
1670 VLC_UNUSED(psz_cmd);
1671 VLC_UNUSED(oldval);
1672 VLC_UNUSED(p_data);
1673 VLC_UNUSED(newval);
1674 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1675 sout_stream_sys_t *p_sys = p_stream->p_sys;
1677 /* TODO: Implement volume change */
1678 VLC_UNUSED(p_sys);
1680 return VLC_SUCCESS;