[PATCH 4/4] sout: add airplay support

Alexander Lyon arlyon at me.com
Tue Oct 9 00:47:52 CEST 2018


---
 configure.ac                          |    5 +
 modules/MODULES_LIST                  |    1 +
 modules/stream_out/Makefile.am        |    8 +
 modules/stream_out/airplay/airplay.c  | 2771 +++++++++++++++++++++++++
 modules/stream_out/airplay/csrp/srp.c |  955 +++++++++
 modules/stream_out/airplay/csrp/srp.h |  233 +++
 6 files changed, 3973 insertions(+)
 create mode 100644 modules/stream_out/airplay/airplay.c
 create mode 100755 modules/stream_out/airplay/csrp/srp.c
 create mode 100755 modules/stream_out/airplay/csrp/srp.h

diff --git a/configure.ac b/configure.ac
index 1627c12b79..e58a5113cd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4072,6 +4072,11 @@ AS_IF([test "${enable_gnutls}" != "no"], [
 ])
 
 
+dnl
+dnl  Airplay plugin
+dnl
+PKG_ENABLE_MODULES_VLC([AIRPLAY], [stream_out_airplay], [libplist libplist >= 2.0.0], [Airplay support], [auto], [${GCRYPT_CFLAGS} ${SOCKET_LIBS}], [${LIBPLIST_LIBS} ${GCRYPT_LIBS}])
+
 dnl
 dnl Taglib plugin
 dnl
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index e1978cc6e4..95fce93fda 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -367,6 +367,7 @@ $Id$
  * stats: Stats encoder function
  * stereo_widen: Enhances stereo effect
  * stl: EBU STL decoder
+ * stream_out_airplay: AirPlay streaming output module
  * stream_out_autodel: monitor mux inputs and automatically add/delete streams
  * stream_out_bridge: "exchange" streams between sout instances. To be used with VLM
  * stream_out_chromaprint: Audio fingerprinter
diff --git a/modules/stream_out/Makefile.am b/modules/stream_out/Makefile.am
index 24a7a6db82..032c329a06 100644
--- a/modules/stream_out/Makefile.am
+++ b/modules/stream_out/Makefile.am
@@ -73,6 +73,14 @@ libstream_out_chromaprint_plugin_la_LIBADD = $(CHROMAPRINT_LIBS)
 EXTRA_LTLIBRARIES += libstream_out_chromaprint_plugin.la
 sout_LTLIBRARIES += $(LTLIBstream_out_chromaprint)
 
+# AirPlay plugin
+if HAVE_GCRYPT
+libstream_out_airplay_plugin_la_SOURCES = stream_out/airplay/airplay.c stream_out/airplay/csrp/srp.c stream_out/airplay/csrp/srp.h
+libstream_out_airplay_plugin_la_CFLAGS = $(GCRYPT_CFLAGS) $(LIBPLIST_CFLAGS) $(AM_CFLAGS) $(CFLAGS_stream_out_airplay)
+libstream_out_airplay_plugin_la_LIBADD = $(GCRYPT_LIBS) -lplist $(LIBS_stream_out_airplay)
+sout_LTLIBRARIES += libstream_out_airplay_plugin.la
+endif
+
 # Chromecast plugin
 SUFFIXES += .proto .pb.cc
 
diff --git a/modules/stream_out/airplay/airplay.c b/modules/stream_out/airplay/airplay.c
new file mode 100644
index 0000000000..b7db2160c5
--- /dev/null
+++ b/modules/stream_out/airplay/airplay.c
@@ -0,0 +1,2771 @@
+/*****************************************************************************
+ * airplay.c: Airplay v1 Streaming Support
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * Author: Alexander Lyon <arlyon at me.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_dialog.h>
+#include <vlc_fixups.h>
+#include <vlc_httpd.h>
+#include <vlc_keystore.h>
+#include <vlc_md5.h>
+#include <vlc_memstream.h>
+#include <vlc_network.h>
+#include <vlc_plugin.h>
+#include <vlc_sout.h>
+#include <vlc_strings.h>
+#include <vlc_stream.h>
+#include <vlc_url.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <gcrypt.h>
+#include <limits.h>
+#include <plist/plist.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "csrp/srp.h"
+
+typedef struct
+{
+    char *psz_deviceid;    // mac address
+    char *psz_model;       // model name
+    uint64_t i_features;   // bit field
+} server_info_t;
+
+typedef struct
+{
+    vlc_mutex_t lock;
+    int i_authenticated_fd;
+    bool b_join;
+    vlc_object_t *p_module;
+} ping_ctx_t;
+
+typedef struct
+{
+    char *psz_bonjour_hostname;
+    char *psz_ipv4_ip;
+    gcry_sexp_t *p_sexp_eddsa_keypair;
+
+    uint16_t i_port;
+    uint64_t i_features;
+
+    vlc_keystore *p_keystore;
+    server_info_t *p_server_info;
+
+    // internal http server
+    char *psz_m3u8;
+    vlc_mutex_t lock_m3u8;
+    httpd_host_t *p_httpd_host;
+    httpd_file_t *p_m3u8_file;
+    sout_stream_t *p_out;
+
+    // auth
+    ping_ctx_t *p_ping_ctx;
+    vlc_thread_t *p_ping_thread;
+
+    // this is used in the ping thread
+    // it is unsafe to make requests over
+    // this fd when the ping thread is running
+    int i_authenticated_fd;
+
+    // stream stuff
+    vlc_array_t *p_streams;
+    vlc_array_t *p_streams_out;
+    bool b_souts_changed;
+    bool b_out_force_reload;
+    bool b_perf_warning_shown;
+    bool b_has_video;
+    bool b_airplay_flushing;
+} sout_stream_sys_t;
+
+typedef struct sout_stream_id_sys_t
+{
+    es_format_t fmt;
+    struct sout_stream_id_sys_t *p_sub_id;
+    bool flushed;
+} sout_stream_id_sys_t;
+
+#define SOUT_CFG_PREFIX "sout-airplay-"
+#define AIRPLAY_USER_AGENT "VLC "VERSION
+#define AIRPLAY_USERNAME "VLC_AIRPLAY"
+
+#if 0
+#define HARDCODED_ED_PUBLIC "f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863"
+#define HARDCODED_ED_PRIVATE "3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073"
+#define HARDCODED_CURVE_PUBLIC "05439b27f204f6298d1250eed5b8055cdae23025f878759a836b28646d18505a"
+#define HARDCODED_CURVE_PRIVATE "5394cd7df72cad8ad9eb71ab02d1a24bdaf72e9744d8ad5aa6179d0f9548d858"
+#endif
+
+#define FEATURE_VIDEO 0x1
+#define FEATURE_PHOTO 0x2
+#define FEATURE_VIDEO_FAIRPLAY 0x4
+#define FEATURE_VIDEO_VOLUME_CONTROL 0x8
+#define FEATURE_VIDEO_HTTP_LIVE_STREAMS 0x10
+#define FEATURE_SLIDESHOW 0x20
+// nothing for bit 6
+#define FEATURE_SCREEN 0x80
+#define FEATURE_SCREEN_ROTATE 0x100
+#define FEATURE_AUDIO 0x200
+// nothing for bit 10
+#define FEATURE_AUDIO_REDUNDANT 0x800
+#define FEATURE_FAIRPLAY_SECURE_AUTH 0x1000
+#define FEATURE_PHOTO_CACHING 0x2000
+
+#define TRANSCODING_NONE 0x0
+#define TRANSCODING_VIDEO 0x1
+#define TRANSCODING_AUDIO 0x2
+
+/*****************************************************************************
+ * Prototypes
+ *****************************************************************************/
+
+// Module Callbacks
+
+static int Open( vlc_object_t * );
+
+static void Close( vlc_object_t * );
+
+static void *Add( sout_stream_t *, const es_format_t * );
+
+static void Del( sout_stream_t *, void * );
+
+static int Send( sout_stream_t *, void *, block_t * );
+
+static void Flush( sout_stream_t *, void * );
+
+// Authentication
+
+static int AirplayVerify( vlc_object_t *p_this, int *i_authenticated_fd );
+
+static int AirplayPair( vlc_object_t *p_this, char *psz_username, uint8_t *p_bytes_ed_pub, size_t i_length_ed_pub );
+
+#ifdef AIRPLAY_DIGEST
+static bool airplay_RequiresDeviceVerification( vlc_object_t *p_this );
+
+static char *airplay_GenerateDigest( vlc_object_t *p_this, const char *psz_realm, const char *psz_nonce,
+                                     const char *psz_password, const char *psz_method, const char *psz_uri );
+
+static int airplay_StoreDigest( vlc_object_t *p_this );
+#endif
+
+// Network
+
+static int16_t DoHttp( vlc_object_t *p_this, int *pi_fd, const char *psz_method, const char *psz_path,
+                       const char *psz_content_type, const char *psz_body, const size_t i_content_length,
+                       const vlc_dictionary_t *p_req_headers, char **ppsz_resp_body,
+                       size_t *pi_resp_content_length, vlc_dictionary_t *p_resp_headers );
+
+static int16_t ParseStatusCode( vlc_object_t *p_this, int i_fd );
+
+static int ParseHeaders( vlc_object_t *p_this, int i_fd, vlc_dictionary_t *headers );
+
+#ifdef AIRPLAY_DIGEST
+static int ParseWWWAuthenticate( char *psz_www_authenticate, char **ppsz_realm_value, char **ppsz_nonce_value );
+#endif
+
+static bool PortIsOpen( vlc_object_t *p_this, char *psz_host, uint16_t i_port );
+
+// Utility
+
+static server_info_t *GetServerInfo( vlc_object_t *p_this );
+
+static inline bool HasFeature( server_info_t *p_info, uint64_t i_feature );
+
+static int StartPlayback( vlc_object_t *p_this, const char *psz_uri );
+
+static int StopPlayback( vlc_object_t *p_this, bool close_connection );
+
+static void *PingThread( void *obj );
+
+static int startSoutChain( vlc_object_t *p_this, vlc_array_t *new_streams, const char *psz_sout_template );
+
+static void stopSoutChain( sout_stream_t *p_stream );
+
+static int m3u8Callback( httpd_file_sys_t *p_args, httpd_file_t *f,
+                         uint8_t const *p_request, uint8_t **pp_data, int *pi_data );
+
+static sout_stream_id_sys_t *GetSubId( const sout_stream_t *p_stream, const sout_stream_id_sys_t *id, bool update );
+
+static void FreeSys( sout_stream_sys_t *p_sys );
+
+static void FreeServerInfo( server_info_t **pp_info );
+
+static int64_t vlc_strtoi( const char *str );
+
+static void ReverseBuffer( uint8_t *buffer, size_t length );
+
+static void DelInternal( sout_stream_t *p_stream, void *_id, bool b_reset_config );
+
+static bool IsFlushing( sout_stream_t *p_stream );
+
+static bool UpdateOutput( const sout_stream_t *p_stream );
+
+static bool CanDecodeAudio( vlc_fourcc_t codec );
+
+static bool CanDecodeVideo( vlc_fourcc_t codec );
+
+// Cryptography
+
+static int GetOrCreateEd25519( vlc_object_t *p_this, gcry_sexp_t **pp_sexp_ed );
+
+static int CreateCurve25519( vlc_object_t *p_this, gcry_sexp_t *p_sexp_curve );
+
+static void CalculatePairAESKeyIV( const struct srp_user *p_user, uint8_t **p_bytes_aes_key,
+                                   uint8_t **p_bytes_aes_IV, size_t *p_i_length_aes_key_IV );
+
+static void CalculateVerifyAESKeyIV( const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+                                     uint8_t **p_bytes_aes_key, uint8_t **p_bytes_aes_IV,
+                                     size_t *p_i_length_aes_key_IV );
+
+static void EncryptEd25519( const struct srp_user *p_user, uint8_t *p_bytes_ed25519, size_t i_length_ed25519,
+                            uint8_t **p_bytes_epk, uint8_t **p_bytes_authTag );
+
+static gcry_error_t CalculateAESSignature( const uint8_t *p_bytes_atv_challenge, size_t i_length_atv_challenge,
+                                           const uint8_t *p_bytes_ed_signature, size_t i_length_ed_signature,
+                                           const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+                                           uint8_t **pp_bytes_aes_signature, size_t *p_i_length_aes_signature );
+
+static gcry_error_t Curve25519DiffieHellman( const uint8_t *p_bytes_a, size_t i_length_a, const uint8_t *p_bytes_B,
+                                             size_t i_length_B, uint8_t **pp_bytes_shared, size_t *p_i_length_shared );
+
+static void EdSignConcatenatedBytes( gcry_sexp_t sexp_ed_keypair, const uint8_t *p_bytes_a,
+                                     size_t i_length_a,
+                                     const uint8_t *p_bytes_b, size_t i_length_b,
+                                     uint8_t **p_bytes_ed_signature, size_t *i_length_ed_signature );
+
+static gcry_error_t CurveDecompress( gcry_mpi_t mpi_compressed, gcry_mpi_t *mpi_uncompressed );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+#define PERF_TEXT N_( "Performance warning" )
+#define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
+
+vlc_module_begin()
+    set_shortname( N_( "AirPlay" ) )
+    set_description( N_( "AirPlay stream output" ) )
+    set_capability( "sout stream", 0 )
+    add_shortcut( "airplay" )
+    set_category( CAT_SOUT )
+    set_subcategory( SUBCAT_SOUT_STREAM )
+    set_callbacks( Open, Close )
+    add_string( SOUT_CFG_PREFIX"ip", NULL, NULL, NULL, false )
+        change_private()
+    add_integer( SOUT_CFG_PREFIX"port", NULL, NULL, NULL, false )
+        change_private()
+    add_integer( SOUT_CFG_PREFIX"show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
+        change_private()
+vlc_module_end()
+
+static const char *const ppsz_sout_options[] = {
+    "ip",
+    "port",
+    NULL
+};
+
+/*****************************************************************************
+ * Module Callbacks
+ *****************************************************************************/
+
+static int Open( vlc_object_t *p_this )
+{
+    gcry_check_version( "1.8.2" );
+    int return_error = VLC_EGENERIC;
+
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
+    if ( p_sys == NULL )
+    {
+        return_error = VLC_ENOMEM;
+        goto error;
+    }
+
+    config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
+
+    p_sys->b_has_video = false;
+    p_sys->i_authenticated_fd = 0;
+    p_sys->i_port = (uint16_t)var_GetInteger( p_stream, SOUT_CFG_PREFIX
+        "port" );
+    if ( p_sys->i_port == 0 )
+    {
+        msg_Err( p_this, "no port specified" );
+        goto error;
+    }
+    if ( ( p_sys->psz_bonjour_hostname = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX
+        "ip" ) ) == NULL )
+    {
+        msg_Err( p_this, "no hostname specified" );
+        goto error;
+    }
+    p_sys->p_keystore = vlc_keystore_create( p_this );
+    vlc_mutex_init( &p_sys->lock_m3u8 );
+
+    if ( ( p_sys->p_streams = calloc( 1, sizeof( *p_sys->p_streams ) ) ) == NULL )
+    {
+        return_error = VLC_ENOMEM;
+        goto error;
+    }
+
+    if ( ( p_sys->p_streams_out = calloc( 1, sizeof( *p_sys->p_streams_out ) ) ) == NULL )
+    {
+        return_error = VLC_ENOMEM;
+        goto error;
+    }
+
+    vlc_array_init( p_sys->p_streams );
+    vlc_array_init( p_sys->p_streams_out );
+
+    p_stream->pf_add = Add;
+    p_stream->pf_del = Del;
+    p_stream->pf_send = Send;
+    p_stream->pf_flush = Flush;
+    p_stream->p_sys = p_sys;
+    p_stream->pace_nocontrol = true;
+
+    // get ipv4 address accessible by apple TV
+    struct addrinfo *res = NULL;
+    struct addrinfo hints = {
+        .ai_family = AF_INET,
+        .ai_socktype = SOCK_STREAM,
+    };
+    vlc_getaddrinfo( p_sys->psz_bonjour_hostname, 7000, &hints, &res );
+
+    char *psz_ipv4_ip = calloc( NI_MAXNUMERICHOST, sizeof( *psz_ipv4_ip ) );
+    int port = 7000;
+
+    vlc_getnameinfo( res->ai_addr, res->ai_addrlen, psz_ipv4_ip, NI_MAXNUMERICHOST, &port, NI_NUMERICHOST );
+    int ipv4_fd = net_ConnectTCP( p_this, psz_ipv4_ip, port );
+    net_GetSockAddress( ipv4_fd, psz_ipv4_ip, &port );
+    p_sys->psz_ipv4_ip = psz_ipv4_ip;
+    net_Close( ipv4_fd );
+
+    if ( ( p_sys->p_server_info = GetServerInfo( p_this ) ) == NULL )
+    {
+        msg_Err( p_this, "Unable to get server info, aborting." );
+        goto error;
+    }
+
+    if ( ( return_error = AirplayVerify( p_this, &p_sys->i_authenticated_fd ) ) != VLC_SUCCESS )
+    {
+        msg_Err( p_this, "Could not authenticate with apple tv, aborting." );
+        goto error;
+    }
+
+    return HasFeature( p_sys->p_server_info, FEATURE_VIDEO | FEATURE_VIDEO_HTTP_LIVE_STREAMS )
+           ? VLC_SUCCESS : VLC_EGENERIC;
+
+    error:
+    if ( p_sys != NULL )
+    {
+        if ( p_sys->p_ping_ctx != NULL ) StopPlayback( p_this, true );
+        FreeSys( p_sys );
+    }
+    return return_error;
+}
+
+static void Close( vlc_object_t *p_this )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    assert(
+        vlc_array_count( p_sys->p_streams ) == 0 &&
+        vlc_array_count( p_sys->p_streams_out ) == 0
+    );
+
+    if ( p_sys->p_ping_ctx != NULL ) StopPlayback( p_this, true );
+    stopSoutChain(p_stream);
+    FreeSys( p_sys );
+}
+
+static void *Add( sout_stream_t *p_stream, const es_format_t *p_fmt )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    if ( !HasFeature( p_sys->p_server_info, FEATURE_VIDEO ) && p_fmt->i_cat != AUDIO_ES )
+    {
+        return NULL;
+    }
+
+    sout_stream_id_sys_t *p_sys_id = calloc( 1, sizeof( *p_sys_id ) );
+    if ( p_sys_id != NULL )
+    {
+        es_format_Copy( &p_sys_id->fmt, p_fmt );
+        p_sys_id->flushed = false;
+
+        vlc_array_append( p_sys->p_streams, p_sys_id );
+        p_sys->b_souts_changed = true;
+    }
+
+    return p_sys_id;
+}
+
+static void Del( sout_stream_t *p_stream, void *_id )
+{
+    sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+    DelInternal( p_stream, id, true );
+}
+
+static int Send( sout_stream_t *p_stream, void *_id, block_t *p_buffer )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+
+
+    if ( IsFlushing( p_stream ) )
+    {
+        block_ChainRelease( p_buffer );
+        return VLC_SUCCESS;
+    }
+
+    // get sub id
+    sout_stream_id_sys_t *p_sub_id = GetSubId( p_stream, id, true );
+    if ( p_sub_id == NULL )
+    {
+        block_ChainRelease( p_buffer );
+        return VLC_EGENERIC;
+    }
+
+    int error = sout_StreamIdSend( p_sys->p_out, p_sub_id, p_buffer );
+    if ( error != VLC_SUCCESS )
+    {
+        DelInternal( p_stream, id, false );
+    }
+
+    return error;
+}
+
+static void Flush( sout_stream_t *p_stream, void *_id )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+
+    sout_stream_id_sys_t *p_sub_id = GetSubId( p_stream, id, false );
+    if ( p_sub_id == NULL )
+        return;
+    p_sub_id->flushed = true;
+
+    if ( !p_sys->b_airplay_flushing )
+    {
+        p_sys->b_airplay_flushing = true;
+        stopSoutChain( p_stream );
+        p_sys->b_out_force_reload = p_sys->b_souts_changed = true;
+    }
+}
+
+/*****************************************************************************
+ * Authentication
+ *****************************************************************************/
+
+static int AirplayVerify( vlc_object_t *p_this, int *i_authenticated_fd )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    vlc_dictionary_t keep_alive_headers;
+    vlc_dictionary_init( &keep_alive_headers, 1 );
+    vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+    gcry_error_t gcry_error = 0;
+    int vlc_error = VLC_SUCCESS;
+    int16_t i_status;
+    int i_fd = 0;
+
+    uint8_t *p_bytes_curve_priv = NULL;
+    size_t i_length_curve_priv;
+    uint8_t *p_bytes_curve_pub = NULL;
+    size_t i_length_curve_pub;
+    uint8_t *p_bytes_curve_shared = NULL;
+    size_t i_length_curve_shared;
+    uint8_t *p_bytes_ed_signature = NULL;
+    size_t i_length_ed_signature;
+    uint8_t *p_bytes_aes_signature = NULL;
+    size_t i_length_aes_signature;
+    uint8_t *p_bytes_response = NULL;
+    size_t i_length_response;
+
+    gcry_mpi_t mpi_ed_pub = NULL;
+    gcry_mpi_t mpi_curve_pub_compressed = NULL;
+    gcry_mpi_t mpi_curve_pub = gcry_mpi_new( 0 );
+    gcry_mpi_t mpi_curve_priv = NULL;
+    gcry_sexp_t sexp_curve_keypair = NULL;
+
+    // get or create Ed25519 pair
+
+    if ( p_sys->p_sexp_eddsa_keypair == NULL )
+    {
+        vlc_error = GetOrCreateEd25519( p_this, &p_sys->p_sexp_eddsa_keypair );
+    }
+
+    if ( vlc_error != VLC_SUCCESS )
+    {
+        msg_Err( p_this, "Could not generate Ed25519 key." );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    if ( ( gcry_error = gcry_sexp_extract_param( *p_sys->p_sexp_eddsa_keypair, NULL, "q", &mpi_ed_pub, NULL ) ) )
+    {
+        msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    // generate Curve ephemeral pair
+
+    if ( ( vlc_error = CreateCurve25519( p_this, &sexp_curve_keypair ) ) != VLC_SUCCESS )
+    {
+        msg_Err( p_this, "Could not generate Curve25519 key." );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    if ( ( gcry_error = gcry_sexp_extract_param( sexp_curve_keypair, NULL, "qd",
+                                                 &mpi_curve_pub_compressed, &mpi_curve_priv, NULL ) ) )
+    {
+        msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    if ( ( gcry_error = CurveDecompress( mpi_curve_pub_compressed, &mpi_curve_pub ) ) )
+    {
+        msg_Err( p_this, "could not decompress curve public key" );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    bool b_step_one = false;
+    uint8_t *p_bytes_step_one = NULL;
+    size_t i_length_step_one;
+
+    while ( b_step_one == false )
+    {
+        uint8_t *p_bytes_curve_pub = NULL;
+        size_t i_length_curve_pub;
+        uint8_t *p_bytes_ed_pub = NULL;
+        size_t i_length_ed_pub;
+        uint8_t *p_bytes_initialize = NULL;
+        size_t i_length_initialize;
+
+
+        gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_pub, &i_length_curve_pub, mpi_curve_pub );
+        ReverseBuffer( p_bytes_curve_pub, i_length_curve_pub );
+        gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_ed_pub, &i_length_ed_pub, mpi_ed_pub );
+
+        // build octet stream - 01 00 00 00 ~ curve pub key ~ eddsa pub key
+        if ( i_length_curve_pub + i_length_ed_pub != 64 )
+        {
+            msg_Err( p_this, "Verify Octet Stream Invalid" );
+            free( p_bytes_curve_pub );
+            free( p_bytes_ed_pub );
+            goto error;
+        }
+
+        i_length_initialize = 4 + i_length_curve_pub + i_length_ed_pub;
+        p_bytes_initialize = calloc( i_length_initialize, sizeof( *p_bytes_initialize ) );
+        p_bytes_initialize[0] = 0x01;
+        p_bytes_initialize[1] = 0x00;
+        p_bytes_initialize[2] = 0x00;
+        p_bytes_initialize[3] = 0x00;
+        memcpy( p_bytes_initialize + 4, p_bytes_curve_pub, i_length_curve_pub );
+        memcpy( p_bytes_initialize + 4 + i_length_curve_pub, p_bytes_ed_pub, i_length_ed_pub );
+
+        i_status = DoHttp( p_this, &i_fd, "POST", "/pair-verify", "application/octet-stream",
+                           (const char *)p_bytes_initialize, i_length_initialize, &keep_alive_headers,
+                           (char **)&p_bytes_step_one, &i_length_step_one, NULL );
+
+        free( p_bytes_curve_pub );
+        free( p_bytes_initialize );
+
+        if ( i_status == 200 )
+        {
+            b_step_one = true;
+        } else
+        {
+            free( p_bytes_step_one );
+            net_Close( i_fd );
+            i_fd = 0;
+
+            if ( ( vlc_error = AirplayPair( p_this, "username", p_bytes_ed_pub, i_length_ed_pub ) ) != VLC_SUCCESS )
+            {
+                vlc_dialog_display_error( p_this, _("Invalid"), _("Invalid code") );
+                free( p_bytes_ed_pub );
+                goto error;
+            }
+        }
+
+        free( p_bytes_ed_pub );
+    }
+
+    // step 2: create a signature with the Ed25519 private key and send to Receiver.
+
+    const size_t i_length_atv_curve_pub = 32;
+    const size_t i_length_atv_challenge = 64;
+    uint8_t *p_bytes_atv_curve_pub = p_bytes_step_one;
+    uint8_t *p_bytes_atv_challenge = p_bytes_step_one + 32;
+
+    // curve 25519 diffie helman -> <shared>
+    // curve 25519 is stored backwards so we need to reverse the buffers..
+
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_priv, &i_length_curve_priv, mpi_curve_priv );
+    ReverseBuffer( p_bytes_curve_priv, i_length_curve_priv );
+
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_pub, &i_length_curve_pub, mpi_curve_pub );
+    ReverseBuffer( p_bytes_curve_pub, i_length_curve_pub );
+
+    Curve25519DiffieHellman( p_bytes_curve_priv, i_length_curve_priv, p_bytes_atv_curve_pub, i_length_atv_curve_pub,
+                             &p_bytes_curve_shared, &i_length_curve_shared );
+
+
+    // Ed25519 sign(Sender Curve Pub ~ Receiver Curve Pub) -> <ed_signature>
+
+    EdSignConcatenatedBytes( *p_sys->p_sexp_eddsa_keypair, p_bytes_curve_pub, i_length_curve_pub, p_bytes_atv_curve_pub,
+                             i_length_atv_curve_pub, &p_bytes_ed_signature, &i_length_ed_signature );
+
+    // create AES
+
+    if ( ( gcry_error = CalculateAESSignature( p_bytes_atv_challenge, i_length_atv_challenge, p_bytes_ed_signature,
+                                               i_length_ed_signature, p_bytes_curve_shared, i_length_curve_shared,
+                                               &p_bytes_aes_signature, &i_length_aes_signature ) ) )
+    {
+        msg_Err( p_this, "gcrypt error when calculating AES signature" );
+        vlc_error = VLC_EGENERIC;
+        goto error;
+    }
+
+    i_length_response = 4 + i_length_aes_signature;
+    p_bytes_response = calloc( i_length_response, sizeof( *p_bytes_response ) );
+    p_bytes_response[0] = 0x00;
+    p_bytes_response[1] = 0x00;
+    p_bytes_response[2] = 0x00;
+    p_bytes_response[3] = 0x00;
+    memcpy( p_bytes_response + 4, p_bytes_aes_signature, i_length_aes_signature );
+
+    i_status = DoHttp( p_this, &i_fd, "POST", "/pair-verify", "application/octet-stream",
+                       (const char *)p_bytes_response, i_length_response, &keep_alive_headers, NULL, NULL, NULL );
+
+    if ( i_status != 200 )
+    {
+        msg_Err( p_this, "Could not verify." );
+        vlc_error = VLC_EGENERIC;
+    } else
+    {
+        if ( i_authenticated_fd != NULL )
+        {
+            *i_authenticated_fd = i_fd;
+        }
+        vlc_error = VLC_SUCCESS;
+    }
+
+    error:
+
+    vlc_dictionary_clear( &keep_alive_headers, NULL, NULL );
+    gcry_mpi_release( mpi_curve_pub );
+
+    if ( p_bytes_step_one != NULL ) free( p_bytes_step_one );
+    if ( mpi_ed_pub != NULL ) gcry_mpi_release( mpi_ed_pub );
+    if ( mpi_curve_pub_compressed != NULL ) gcry_mpi_release( mpi_curve_pub_compressed );
+    if ( mpi_curve_priv != NULL ) gcry_mpi_release( mpi_curve_priv );
+    if ( sexp_curve_keypair != NULL ) gcry_sexp_release( sexp_curve_keypair );
+    if ( p_bytes_curve_priv != NULL ) free( p_bytes_curve_priv );
+    if ( p_bytes_curve_pub != NULL ) free( p_bytes_curve_pub );
+    if ( p_bytes_curve_shared != NULL ) free( p_bytes_curve_shared );
+    if ( p_bytes_ed_signature != NULL ) free( p_bytes_ed_signature );
+    if ( p_bytes_aes_signature != NULL ) free( p_bytes_aes_signature );
+    if ( p_bytes_response != NULL ) free( p_bytes_response );
+
+    return vlc_error;
+}
+
+static int AirplayPair( vlc_object_t *p_this, char *psz_username, uint8_t *p_bytes_ed_pub, size_t i_length_ed_pub )
+{
+    int vlc_error = VLC_EGENERIC;
+    int16_t i_status;
+
+    uint8_t *p_bytes_plist = NULL;
+    char *psz_I = NULL;
+    char *psz_P = NULL;
+    uint32_t i_content_length;
+    size_t i_out_content_length;
+    vlc_dictionary_t response_headers;
+
+    int i_fd = 0;
+    vlc_dictionary_t keep_alive_headers;
+    vlc_dictionary_init( &keep_alive_headers, 1 );
+    vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+    // post to start pair
+    i_status = DoHttp( p_this, &i_fd, "POST", "/pair-pin-start", NULL, NULL, 0, &keep_alive_headers, NULL, NULL, NULL );
+
+    vlc_error = vlc_dialog_wait_login( p_this, &psz_I, &psz_P, NULL, psz_username, _("Enter Code"),
+                                       _("Enter the code on the selected AirPlay device into the password field.") );
+
+    if ( vlc_error == 0 )
+        msg_Err( p_this, "Verify was cancelled by user." );
+    if ( vlc_error < 1 )
+        goto error;
+
+    // post binary plist {method: "pin", user: I}
+
+    plist_t plist = plist_new_dict();
+    plist_t method = plist_new_string( "pin" );
+    plist_t user = plist_new_string( psz_I );
+    plist_dict_set_item( plist, "method", method );
+    plist_dict_set_item( plist, "user", user );
+    plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+    i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+                       (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, (char **)&p_bytes_plist,
+                       &i_out_content_length, &response_headers );
+    i_content_length = (uint32_t)i_out_content_length;
+
+    plist_free( plist );
+
+    plist_from_bin( (const char *)p_bytes_plist, i_content_length, &plist );
+    free( p_bytes_plist );
+    vlc_dictionary_clear( &response_headers, NULL, NULL );
+    p_bytes_plist = NULL;
+
+    uint8_t *p_bytes_B = NULL;
+    uint8_t *p_bytes_s = NULL;
+    uint64_t i_length_B;
+    uint64_t i_length_s;
+    plist_get_data_val( plist_dict_get_item( plist, "pk" ), (char **)&p_bytes_B, &i_length_B );
+    plist_get_data_val( plist_dict_get_item( plist, "salt" ), (char **)&p_bytes_s, &i_length_s );
+
+    // create user, calculate first ephemeral key (A) and client session key verifier (M1)
+
+    struct srp_user *p_user = apple_srp_user_new( psz_I, strlen( psz_I ), psz_P, strlen( psz_P ) );
+
+    uint8_t *p_bytes_A = NULL;
+    size_t i_A_length = 0;
+    srp_user_start_authentication( p_user, NULL, &p_bytes_A, &i_A_length );
+
+    uint8_t *p_bytes_M1 = NULL;
+    size_t i_M1_length = 0;
+    srp_user_process_challenge( p_user, p_bytes_s, i_length_s, p_bytes_B, i_length_B, &p_bytes_M1, &i_M1_length );
+
+    // post binary plist {pk: A, proof: M1}
+
+    plist = plist_new_dict();
+    plist_t srp6_A = plist_new_data( (const char *)p_bytes_A, i_A_length );
+    plist_t srp6_M1 = plist_new_data( (const char *)p_bytes_M1, i_M1_length );
+    plist_dict_set_item( plist, "pk", srp6_A );
+    plist_dict_set_item( plist, "proof", srp6_M1 );
+    plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+    i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+                       (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, (char **)&p_bytes_plist,
+                       &i_out_content_length, &response_headers );
+    i_content_length = (uint32_t)i_out_content_length;
+
+    plist_free( plist );
+
+    if ( i_status != 200 )
+    {
+        msg_Dbg( p_this, "Server authentication failed!\n" );
+        return VLC_EGENERIC;
+    }
+
+    // get proof (M2) from response
+
+    plist_from_bin( (const char *)p_bytes_plist, (uint32_t)i_content_length, &plist );
+    vlc_dictionary_clear( &response_headers, NULL, NULL );
+
+    uint8_t *p_bytes_M2 = NULL;
+    uint64_t i_length_M2;
+    plist_get_data_val( plist_dict_get_item( plist, "proof" ), (char **)&p_bytes_M2, &i_length_M2 );
+
+    plist_free( plist );
+    free( p_bytes_plist );
+    p_bytes_plist = NULL;
+
+    if ( !srp_user_verify_session( p_user, p_bytes_M2 ) )
+    {
+        msg_Dbg( p_this, "Server authentication failed!\n" );
+        return VLC_EGENERIC;
+    }
+
+    // encrypt Ed25519 public key and send it
+
+    uint8_t *p_epk;
+    uint8_t *p_authTag;
+    const uint8_t i_authTag_length = 16;
+
+    EncryptEd25519( p_user, p_bytes_ed_pub, i_length_ed_pub, &p_epk, &p_authTag );
+
+    // create and post plist
+
+    plist = plist_new_dict();
+    plist_t epk = plist_new_data( (const char *)p_epk, i_length_ed_pub );
+    plist_t authTag = plist_new_data( (const char *)p_authTag, i_authTag_length );
+    plist_dict_set_item( plist, "epk", epk );
+    plist_dict_set_item( plist, "authTag", authTag );
+    plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+    i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+                       (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, NULL, NULL,
+                       &response_headers );
+
+    plist_free( plist );
+    vlc_dictionary_clear( &response_headers, NULL, NULL );
+
+    if ( i_status == 200 )
+        vlc_error = VLC_SUCCESS;
+
+    error:
+    if ( p_bytes_plist != NULL ) free( p_bytes_plist );
+    if ( psz_I != NULL ) free( psz_I );
+    if ( psz_P != NULL ) free( psz_P );
+    return vlc_error;
+}
+
+#ifdef AIRPLAY_DIGEST
+/**
+ * Checks if the given connection requires device verification.
+ * @return True if device verification is necessary.
+ * @note Device verification is mandatory as of tvOS10.2
+ */
+static bool airplay_RequiresDeviceVerification( vlc_object_t *p_this )
+{
+    int64_t i_status = airplay_DoHttp( p_this, "POST", "/play", "application/x-apple-binary-plist", NULL, 0, NULL,
+                                       NULL, false );
+    return i_status == 403 ? true : false;
+}
+
+/**
+ * Creates the auth header for Airplay from a given nonce.
+ * @param psz_realm The realm for the request.
+ * @param psz_nonce The nonce from the server.
+ * @param psz_password The password (code).
+ * @param psz_method The request method.
+ * @param psz_uri The URI.
+ * @return The Authorization header.
+ * @note Calculated as follows -> hash(hash("username:realm:password")+hash("method:uri"))
+ */
+static char *airplay_GenerateDigest(
+        vlc_object_t *p_this,
+        const char *psz_realm,
+        const char *psz_nonce,
+        const char *psz_password,
+        const char *psz_method,
+        const char *psz_uri )
+{
+    struct md5_s p_first_md5;
+
+    InitMD5( &p_first_md5 );
+    AddMD5( &p_first_md5, AIRPLAY_USERNAME, strlen( AIRPLAY_USERNAME ) );
+    AddMD5( &p_first_md5, ":", 1 );
+    AddMD5( &p_first_md5, psz_realm, strlen( psz_realm ) );
+    AddMD5( &p_first_md5, ":", 1 );
+    AddMD5( &p_first_md5, psz_password, strlen( psz_password ) );
+    EndMD5( &p_first_md5 );
+
+    char *psz_first_hash = psz_md5_hash( &p_first_md5 );
+    msg_Dbg( p_this, "Calculated hash 1: "
+            AIRPLAY_USERNAME
+            ":%s:%s -> %s", psz_realm, psz_password, psz_first_hash );
+
+    struct md5_s p_second_md5;
+
+    InitMD5( &p_second_md5 );
+    AddMD5( &p_second_md5, psz_method, strlen( psz_method ) );
+    AddMD5( &p_second_md5, ":", 1 );
+    AddMD5( &p_second_md5, psz_uri, strlen( psz_uri ) );
+    EndMD5( &p_second_md5 );
+
+    char *psz_second_hash = psz_md5_hash( &p_second_md5 );
+    msg_Dbg( p_this, "Calculated hash 2: %s:%s -> %s", psz_method, psz_uri, psz_second_hash );
+
+    struct md5_s p_final_md5;
+
+    InitMD5( &p_final_md5 );
+    AddMD5( &p_final_md5, psz_first_hash, strlen( psz_first_hash ) );
+    AddMD5( &p_final_md5, ":", 1 );
+    AddMD5( &p_final_md5, psz_nonce, strlen( psz_nonce ) );
+    AddMD5( &p_final_md5, ":", 1 );
+    AddMD5( &p_final_md5, psz_second_hash, strlen( psz_second_hash ) );
+    EndMD5( &p_final_md5 );
+
+    char *response = psz_md5_hash( &p_final_md5 );
+    msg_Dbg( p_this, "Calculated final hash: %s:%s:%s -> %s", psz_first_hash, psz_nonce, psz_second_hash,
+             response );
+    return response;
+}
+
+static int airplay_StoreDigest( vlc_object_t *p_this )
+{
+    vlc_dictionary_t resp_headers;
+    int64_t i_status = airplay_DoHttp( p_this, "POST", "/play", NULL, NULL, 0, NULL, &resp_headers, false );
+    while ( i_status == 401 )
+    {
+        char *psz_username;
+        char *psz_code;
+        int err = vlc_dialog_wait_login( p_this, &psz_username, &psz_code, NULL, NULL, _("Enter Code"),
+                                         _("Enter the code on the selected AirPlay device into the password field.") );
+        if ( err < 1 ) return VLC_EGENERIC;
+        else free( psz_username );
+
+        char *psz_realm, *psz_nonce;
+        ParseWWWAuthenticate( (char *)vlc_dictionary_value_for_key( &resp_headers, "WWW-Authenticate" ),
+                                      &psz_realm,
+                                      &psz_nonce );
+
+        char *response = airplay_GenerateDigest( p_this, psz_realm, psz_nonce, psz_code, "POST", "/play" );
+
+        vlc_dictionary_clear( &resp_headers, NULL, NULL );
+        i_status = airplay_DoHttp( p_this, "POST", "/play", "application/x-apple-binary-plist", NULL, 0, NULL,
+                                   &resp_headers, false );
+
+        if ( i_status != 200 )
+        {
+            vlc_dialog_display_error( p_this, _("Invalid"), _("Invalid code") );
+            msg_Dbg( p_this, "Authentication failed: %d", i_status );
+        }
+
+        free( psz_realm );
+        free( psz_nonce );
+        free( psz_code );
+        free( response );
+    }
+}
+# endif
+
+/*****************************************************************************
+ * Network
+ *****************************************************************************/
+
+static int16_t
+DoHttp( vlc_object_t *p_this, int *pi_fd, const char *psz_method, const char *psz_path, const char *psz_content_type,
+        const char *psz_body, const size_t i_content_length, const vlc_dictionary_t *p_req_headers,
+        char **ppsz_resp_body, size_t *pi_resp_content_length, vlc_dictionary_t *p_resp_headers )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    int i_fd;
+    bool free_headers = false;
+
+    if ( p_resp_headers == NULL )
+    {
+        p_resp_headers = calloc( 1, sizeof( *p_resp_headers ) );
+        free_headers = true;
+    }
+
+    if ( psz_body != NULL ) assert( psz_content_type != NULL );
+
+    struct vlc_memstream stream;
+    vlc_memstream_open( &stream );
+
+    vlc_memstream_printf( &stream, "%s %s HTTP/1.1\r\n", psz_method, psz_path );
+    vlc_memstream_puts( &stream, "User-Agent: "
+        AIRPLAY_USER_AGENT
+        "\r\n" );
+
+    if ( p_req_headers != NULL )
+    {
+        char **ppsz_keys = vlc_dictionary_all_keys( p_req_headers );
+        for ( int i = 0; i < vlc_dictionary_keys_count( p_req_headers ); ++i )
+        {
+            char *psz_key = ppsz_keys[i];
+            char *psz_value = (char *)vlc_dictionary_value_for_key( p_req_headers, ppsz_keys[i] );
+            vlc_memstream_printf( &stream, "%s: %s\r\n", psz_key, psz_value );
+            free( psz_key );
+        }
+        free( ppsz_keys );
+    }
+
+    if ( psz_body != NULL && psz_content_type != NULL )
+    {
+        vlc_memstream_printf( &stream, "Content-Type: %s\r\n", psz_content_type );
+        vlc_memstream_printf( &stream, "Content-Length: %zu\r\n", i_content_length );
+        vlc_memstream_puts( &stream, "\r\n" );
+        vlc_memstream_puts_len( &stream, psz_body, i_content_length );
+    } else
+    {
+        vlc_memstream_puts( &stream, "\r\n" );
+    }
+
+    if ( vlc_memstream_close( &stream ) )
+    {
+        return VLC_ENOMEM;
+    }
+
+    // if the pointer is null, or the supplied file descriptor is null, generate one.
+    if ( pi_fd == NULL || *pi_fd == 0 )
+    {
+        i_fd = net_ConnectTCP( p_stream, p_sys->psz_bonjour_hostname, p_sys->i_port );
+        if ( i_fd == -1 )
+        {
+            msg_Err( p_this, "Could not open connection with AppleTV" );
+            return VLC_EGENERIC;
+        }
+    } else
+    {
+        i_fd = *pi_fd;
+    }
+
+    // if the supplied fd was null, set the supplied fd to the generated one
+    if ( pi_fd != NULL && *pi_fd == 0 )
+    {
+        *pi_fd = i_fd;
+    }
+
+    ssize_t bytes_written = net_Write( p_this, i_fd, stream.ptr, stream.length );
+    free( stream.ptr );
+
+    if ( bytes_written != (ssize_t)stream.length )
+    {
+        msg_Err( p_this, "could not write to file descriptor %d", i_fd );
+        return VLC_EGENERIC;
+    }
+
+    int16_t i_status = ParseStatusCode( p_this, i_fd );
+    ParseHeaders( p_this, i_fd, p_resp_headers );
+
+    msg_Dbg( p_this, "Sent %zd bytes to %s on port %d (status %"
+        PRId16
+        ")",
+             bytes_written, p_sys->psz_bonjour_hostname, p_sys->i_port, i_status );
+
+    size_t i_resp_content_length = vlc_strtoi( vlc_dictionary_value_for_key( p_resp_headers, "Content-Length" ) );
+    if ( ppsz_resp_body != NULL && i_resp_content_length > 0 )
+    {
+        if ( *ppsz_resp_body != NULL )
+        {
+            free( *ppsz_resp_body );
+            *ppsz_resp_body = NULL;
+        }
+        *ppsz_resp_body = calloc( i_resp_content_length, sizeof( **ppsz_resp_body ) );
+        net_Read( p_this, i_fd, *ppsz_resp_body, i_resp_content_length );
+    } else if ( i_resp_content_length > 0 )
+    {
+        // read till end
+        size_t to_read = i_resp_content_length;
+        char dump[100];
+        while ( to_read > 0 )
+        {
+            to_read -= net_Read( p_this, i_fd, dump, to_read < 100 ? to_read : 100 );
+        }
+    }
+
+    if ( pi_resp_content_length != NULL )
+    {
+        *pi_resp_content_length = i_resp_content_length;
+    }
+
+    if ( pi_fd == NULL )
+    {
+        net_Close( i_fd );
+    }
+
+    if ( free_headers )
+    {
+        vlc_dictionary_clear( p_resp_headers, NULL, NULL );
+        free( p_resp_headers );
+    }
+
+    return i_status;
+}
+
+static int16_t ParseStatusCode( vlc_object_t *p_this, int i_fd )
+{
+    int64_t i_status = VLC_EGENERIC;
+    char *psz_working = NULL;
+    char *psz_token = NULL;
+
+    char *psz_status = net_Gets( p_this, i_fd );
+    if ( psz_status == NULL )
+        goto error;
+
+    psz_working = strdup( psz_status );
+    psz_token = strsep( &psz_working, " " );
+    if ( !psz_token || !( strncmp( psz_token, "HTTP", 4 ) == 0 || strncmp( psz_token, "RTSP", 4 ) == 0 ) )
+    {
+        msg_Err( p_this, "Unknown protocol (%s)", psz_status );
+        goto error;
+    }
+
+    psz_token = strsep( &psz_working, " " );
+    if ( !psz_token )
+    {
+        msg_Err( p_this, "Request failed (%s)", psz_status );
+        goto error;
+    }
+
+    i_status = vlc_strtoi( psz_token );
+    if ( i_status > 600 || i_status < -600 )
+        i_status = VLC_EGENERIC;
+
+    error:
+    if ( psz_status != NULL ) free( psz_status );
+    return (int16_t)i_status;
+}
+
+static int ParseHeaders( vlc_object_t *p_this, int i_fd, vlc_dictionary_t *headers )
+{
+    char *psz_line = NULL;
+    char *psz_line_copy = NULL;
+    char *psz_token;
+
+    char *psz_key;
+    char *psz_value;
+
+    if ( headers != NULL )
+        vlc_dictionary_init( headers, 0 );
+
+    psz_line = net_Gets( p_this, i_fd );
+    while ( psz_line != NULL && psz_line[0] != '\0' && strncmp( psz_line, "\r\n", 2 ) != 0 )
+    {
+        psz_line_copy = strdup( psz_line );
+        psz_token = strsep( &psz_line, ":" );
+
+        if ( psz_token == NULL || psz_line[0] != ' ' )
+        {
+            msg_Err( p_this, "Invalid header format (%s)", psz_line_copy );
+            return VLC_EGENERIC;
+        }
+
+        psz_key = psz_token;
+        psz_value = psz_line + 1;
+
+        if ( headers != NULL )
+            vlc_dictionary_insert( headers, psz_key, strdup( psz_value ) );
+
+        free( psz_line_copy );
+        psz_line = net_Gets( p_this, i_fd );
+    }
+
+    return VLC_SUCCESS;
+}
+
+#ifdef AIRPLAY_DIGEST
+/**
+ * Extracts the realm and nonce from a WWW-Authenticate header.
+ * @param psz_www_authenticate The value of a WWW-Authenticate header: Digest realm="airplay", nonce="MTUyNzAwODYxNCAPT9wiaibdj88Fccpz8aCD"
+ * @param ppsz_realm
+ * @param ppsz_nonce
+ */
+static int ParseWWWAuthenticate( char *psz_www_authenticate, char **ppsz_realm_value, char **ppsz_nonce_value )
+{
+    if ( strncmp( psz_www_authenticate, "Digest ", 7 ) != 0 ) return VLC_EGENERIC;
+
+    psz_www_authenticate += 7;
+    char *psz_nonce;
+    char *psz_realm = strtok_r( psz_www_authenticate, "; ", &psz_nonce );
+
+    *ppsz_nonce_value = calloc( sizeof( char ), strlen( psz_nonce ) );
+    *ppsz_realm_value = calloc( sizeof( char ), strlen( psz_realm ) );
+
+    sscanf( psz_nonce, "%*[^\"]\"%[^\"]\"", *ppsz_nonce_value );
+    sscanf( psz_realm, "%*[^\"]\"%[^\"]\"", *ppsz_realm_value );
+
+    return VLC_SUCCESS;
+}
+#endif
+
+/*****************************************************************************
+ * Utility
+ *****************************************************************************/
+
+static server_info_t *GetServerInfo( vlc_object_t *p_this )
+{
+    server_info_t *p_info = NULL;
+    vlc_dictionary_t headers;
+    char *psz_body = NULL;
+    size_t i_content_length;
+    int16_t status;
+
+    status = DoHttp( p_this, NULL, "GET", "/info", NULL, NULL, 0, NULL, &psz_body, &i_content_length, &headers );
+
+    if ( status != 200 )
+    {
+        msg_Err( p_this, "Unexpected response from Apple TV" );
+        return NULL;
+    }
+
+    plist_t p_plist = NULL;
+    if ( plist_is_binary( psz_body, i_content_length ) )
+        plist_from_bin( psz_body, i_content_length, &p_plist );
+    else
+        plist_from_xml( psz_body, i_content_length, &p_plist );
+
+    if ( p_plist == NULL )
+    {
+        msg_Err( p_this, "Server produced invalid response" );
+        return NULL;
+    }
+
+    p_info = calloc( 1, sizeof( *p_info ) );
+    plist_get_string_val( plist_dict_get_item( p_plist, "deviceID" ), &p_info->psz_deviceid );
+    plist_get_string_val( plist_dict_get_item( p_plist, "model" ), &p_info->psz_model );
+    plist_get_uint_val( plist_dict_get_item( p_plist, "features" ), &p_info->i_features );
+
+    vlc_dictionary_clear( &headers, NULL, NULL );
+    plist_free( p_plist );
+
+    free( psz_body );
+    return p_info;
+}
+
+static sout_stream_id_sys_t *GetSubId( const sout_stream_t *p_stream, const sout_stream_id_sys_t *id, bool update )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    if ( update && UpdateOutput( p_stream ) == false )
+    {
+        return NULL;
+    }
+
+    for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); ++i )
+    {
+        sout_stream_id_sys_t *stream_out_id = (sout_stream_id_sys_t *)vlc_array_item_at_index(
+            p_sys->p_streams_out, i );
+        if ( id == stream_out_id )
+        {
+            return stream_out_id->p_sub_id;
+        }
+    }
+    return NULL;
+}
+
+static bool CanDecodeAudio( vlc_fourcc_t codec )
+{
+    return codec == VLC_CODEC_MP3 ||
+           codec == VLC_CODEC_MP4A;
+}
+
+static bool CanDecodeVideo( vlc_fourcc_t codec )
+{
+    return codec == VLC_CODEC_H264;
+}
+
+static bool UpdateOutput( const sout_stream_t *p_stream )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    if ( !p_sys->b_souts_changed )
+    {
+        return true;
+    }
+
+    p_sys->b_souts_changed = false;
+
+    bool b_supports_video =
+        ( p_sys->p_server_info->i_features & FEATURE_VIDEO ) > 0 &&
+        ( p_sys->p_server_info->i_features & FEATURE_VIDEO_HTTP_LIVE_STREAMS ) > 0;
+    bool b_supports_audio =
+        ( p_sys->p_server_info->i_features & FEATURE_AUDIO ) > 0;
+
+    bool canRemux = true;
+    vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
+    const es_format_t *p_original_audio = NULL;
+    const es_format_t *p_original_video = NULL;
+    bool b_out_streams_changed = false;
+    vlc_array_t *new_streams = calloc( 1, sizeof( *new_streams ) );
+    vlc_array_init( new_streams );
+
+    sout_stream_id_sys_t *p_stream_id;
+    for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams ); ++i )
+    {
+        p_stream_id = vlc_array_item_at_index( p_sys->p_streams, i );
+        const es_format_t *p_es = &p_stream_id->fmt;
+        if ( b_supports_audio && p_es->i_cat == AUDIO_ES && p_original_audio == NULL )
+        {
+            if ( !CanDecodeAudio( p_es->i_codec ) )
+            {
+                msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char *)&p_es->i_codec );
+                canRemux = false;
+            } else if ( i_codec_audio == 0 )
+            {
+                i_codec_audio = p_es->i_codec;
+            }
+            p_original_audio = p_es;
+            vlc_array_append( new_streams, p_stream_id );
+        } else if ( b_supports_video && p_es->i_cat == VIDEO_ES )
+        {
+            if ( !CanDecodeVideo( p_es->i_codec ) )
+            {
+                msg_Dbg( p_stream, "can't remux video track %d codec %4.4s", p_es->i_id, (const char *)&p_es->i_codec );
+                canRemux = false;
+            } else if ( i_codec_video == 0 )
+            {
+                i_codec_video = p_es->i_codec;
+            }
+            p_original_video = p_es;
+            vlc_array_append( new_streams, p_stream_id );
+        } else
+        {
+            continue;
+        }
+
+        for ( size_t j = 0; j < vlc_array_count( p_sys->p_streams_out ) && !p_sys->b_out_force_reload; ++j )
+        {
+            if ( vlc_array_item_at_index( p_sys->p_streams_out, j ) == p_stream_id )
+            {
+                b_out_streams_changed = true;
+                break;
+            }
+        }
+    }
+
+    if ( vlc_array_count( new_streams ) == 0 )
+    {
+        StopPlayback( p_stream, false );
+        return true;
+    }
+
+    if ( !p_sys->b_out_force_reload && vlc_array_count( new_streams ) == vlc_array_count( p_sys->p_streams_out ) &&
+         !b_out_streams_changed )
+    {
+        return true;
+    }
+
+    p_sys->b_out_force_reload = false;
+
+    char *psz_sout_template = NULL;
+    if ( !canRemux )
+    {
+        if ( !p_sys->b_perf_warning_shown && i_codec_video == 0 && p_original_video
+             && var_InheritInteger( p_stream, SOUT_CFG_PREFIX
+            "show-perf-warning" ) )
+        {
+            int res = vlc_dialog_wait_question( p_stream,
+                                                VLC_DIALOG_QUESTION_WARNING,
+                                                _( "Cancel" ), _( "OK" ), _( "Ok, Don't warn me again" ),
+                                                _( "Performance warning" ),
+                                                _( "Casting this video requires conversion. "
+                                                   "This conversion can use all the available power and "
+                                                   "could quickly drain your battery." ) );
+            if ( res <= 0 )
+                return false;
+            p_sys->b_perf_warning_shown = true;
+            if ( res == 2 )
+                config_PutInt( SOUT_CFG_PREFIX"show-perf-warning", 0 );
+        }
+
+        bool b_transcode_video = i_codec_video == 0 && p_original_video;
+        bool b_transcode_audio = i_codec_audio == 0 && p_original_audio;
+        psz_sout_template = "transcode{%s%s}:http{dst=%s,mux=ts}";
+        char *psz_tmp = NULL;
+
+        asprintf( &psz_tmp, psz_sout_template, b_transcode_audio ? "acodec=mp3 ," : "" );
+        free( psz_sout_template );
+        psz_sout_template = psz_tmp;
+        psz_tmp = NULL;
+
+        asprintf( &psz_tmp, psz_sout_template, b_transcode_video ? "vcodec=h264,maxwidth=1920,maxheight=1080," : "" );
+        free( psz_sout_template );
+        psz_sout_template = psz_tmp;
+        psz_tmp = NULL;
+    } else
+    {
+        psz_sout_template = "http{dst=%s,mux=ts}";
+    }
+
+    int err = startSoutChain( (vlc_object_t *)p_stream, new_streams, psz_sout_template );
+    if ( err != VLC_SUCCESS )
+    {
+        StopPlayback( p_stream, false );
+        return false;
+    }
+    return true;
+}
+
+static void DelInternal( sout_stream_t *p_stream, void *_id, bool b_reset_config )
+{
+    sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)_id;
+    sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+    size_t i_streams_handled = 0;
+    sout_stream_id_sys_t *p_sys_id = NULL;
+    while ( i_streams_handled < vlc_array_count( p_sys->p_streams ) )
+    {
+        p_sys_id = vlc_array_item_at_index( p_sys->p_streams, i_streams_handled );
+        if ( p_sys_id == id )
+        {
+            if ( p_sys_id->p_sub_id != NULL )
+            {
+                sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
+
+                size_t i_streams_out_handled = 0;
+                sout_stream_id_sys_t *p_sys_out_id = NULL;
+                while ( i_streams_out_handled < vlc_array_count( p_sys->p_streams_out ) )
+                {
+                    p_sys_out_id = vlc_array_item_at_index( p_sys->p_streams_out, i_streams_out_handled );
+                    if ( p_sys_out_id == id )
+                    {
+                        vlc_array_remove( p_sys->p_streams_out, i_streams_out_handled );
+                        p_sys->b_souts_changed = b_reset_config;
+                        p_sys->b_out_force_reload = b_reset_config;
+                        if ( p_sys_id->fmt.i_cat == VIDEO_ES )
+                            p_sys->b_has_video = false;
+                        break;
+                    } else
+                    {
+                        i_streams_out_handled++;
+                    }
+                }
+            }
+
+            es_format_Clean( &p_sys_id->fmt );
+            free( p_sys_id );
+            vlc_array_remove( p_sys->p_streams, i_streams_handled );
+            break;
+        } else
+        {
+            i_streams_handled++;
+        }
+    }
+
+    if ( vlc_array_count( p_sys->p_streams ) == 0 )
+        StopPlayback( p_stream, false );
+}
+
+static bool IsFlushing( sout_stream_t *p_stream )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    if ( p_sys->b_airplay_flushing )
+    {
+        return false;
+    }
+
+    for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+    {
+        const sout_stream_id_sys_t *p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+        if ( p_stream_id->flushed )
+            return true;
+    }
+
+    p_sys->b_airplay_flushing = false;
+
+    for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+    {
+        sout_stream_id_sys_t *p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+        p_stream_id->flushed = false;
+    }
+
+    return false;
+}
+
+static int startSoutChain( vlc_object_t *p_this, vlc_array_t *new_streams, const char *psz_sout_template )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    stopSoutChain( p_stream );
+    p_sys->b_has_video = false;
+    p_sys->p_streams_out = new_streams;
+
+    uint16_t i_stream_port = 0;
+    uint16_t i_playlist_port = 0;
+    do
+    {
+        i_stream_port = (uint16_t)( rand() % 65536 );
+    } while ( i_stream_port < 10000 || !PortIsOpen( p_this, p_sys->psz_ipv4_ip, i_stream_port ) );
+
+    do
+    {
+        i_playlist_port = (uint16_t)( rand() % 65536 );
+    } while ( i_playlist_port < 10000 || !PortIsOpen( p_this, p_sys->psz_ipv4_ip, i_playlist_port ) );
+
+    vlc_url_t uri = {0};
+    uri.psz_protocol = "http";
+    uri.psz_host = p_sys->psz_ipv4_ip;
+    uri.i_port = i_stream_port;
+    uri.psz_path = "/out.ts";
+
+    char *psz_uri = vlc_uri_compose( &uri );
+    char *psz_sout = NULL;
+    asprintf( &psz_sout, psz_sout_template,
+              psz_uri + strlen( "http://" ) ); // we don't want the 'http://' protocol prefix
+    msg_Info( p_this, "creating chain %s", psz_sout );
+
+    p_sys->p_out = sout_StreamChainNew( p_stream->p_sout, psz_sout, NULL, NULL );
+    if ( p_sys->p_out == NULL )
+    {
+        msg_Dbg( p_this, "could not create sout chain:%s", psz_sout );
+        vlc_array_clear( p_sys->p_streams_out );
+        free( psz_sout );
+        free( psz_uri );
+        return VLC_EGENERIC;
+    }
+
+    var_Create( p_stream, "http-port", VLC_VAR_INTEGER );
+    var_SetInteger( p_stream, "http-port", i_playlist_port );
+    var_Create( p_stream, "http-host", VLC_VAR_STRING );
+    var_SetString( p_stream, "http-host", p_sys->psz_ipv4_ip );
+    p_sys->p_httpd_host = vlc_http_HostNew( VLC_OBJECT( p_stream ) );
+    if ( p_sys->p_httpd_host == NULL )
+    {
+        msg_Err( p_this, "could not open http server. It's likely the port wasn't closed after checking." );
+        return VLC_EGENERIC;
+    }
+
+    char *psz_m3u8 = NULL;
+    asprintf( &psz_m3u8,
+              "#EXTM3U\n"
+              "\n"
+              "#EXT-X-VERSION:3\n"
+              "\n"
+              "\n"
+              "\n"
+              "#EXTINF:0,\n"
+              "\n"
+              "%s",
+              psz_uri );
+
+    p_sys->psz_m3u8 = psz_m3u8;
+    uri.psz_path = "/playlist.m3u8";
+    p_sys->p_m3u8_file = httpd_FileNew( p_sys->p_httpd_host, uri.psz_path, "application/x-mpegURL", NULL, NULL,
+                                        (httpd_file_callback_t)m3u8Callback, (void *)p_sys );
+    if ( p_sys->p_m3u8_file == NULL )
+    {
+        msg_Err( p_this, "could not add playlist to server." );
+        httpd_HostDelete( p_sys->p_httpd_host );
+        p_sys->p_httpd_host = NULL;
+        return VLC_EGENERIC;
+    }
+
+    uri.i_port = i_playlist_port;
+    psz_uri = vlc_uri_compose( &uri );
+    msg_Info( p_this, "serving playlist on %s", psz_uri );
+
+    size_t i_streams_handled = 0;
+    sout_stream_id_sys_t *p_stream_id;
+    while ( i_streams_handled < vlc_array_count( p_sys->p_streams_out ) )
+    {
+        p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i_streams_handled );
+        p_stream_id->p_sub_id = sout_StreamIdAdd( p_sys->p_out, &p_stream_id->fmt );
+        if ( p_stream_id->p_sub_id == NULL )
+        {
+            msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_stream_id->fmt.i_codec );
+            es_format_Clean( &p_stream_id->fmt );
+            vlc_array_remove( p_sys->p_streams_out, i_streams_handled );
+        } else
+        {
+            if ( p_stream_id->fmt.i_cat == VIDEO_ES )
+            {
+                p_sys->b_has_video = true;
+            }
+            i_streams_handled += 1;
+        }
+    }
+
+    if ( vlc_array_count( p_sys->p_streams_out ) == 0 )
+    {
+        stopSoutChain( p_stream );
+        return VLC_EGENERIC;
+    }
+
+    return StartPlayback( p_this, psz_uri );
+}
+
+static void stopSoutChain( sout_stream_t *p_stream )
+{
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+    if ( p_sys->p_m3u8_file != NULL )
+    {
+        httpd_FileDelete( p_sys->p_m3u8_file );
+        p_sys->p_m3u8_file = NULL;
+    }
+
+    if ( p_sys->p_httpd_host != NULL )
+    {
+        httpd_HostDelete( p_sys->p_httpd_host );
+        p_sys->p_httpd_host = NULL;
+    }
+
+    if ( unlikely( p_sys->p_out != NULL ) )
+    {
+        sout_stream_id_sys_t *p_stream_id;
+        for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+        {
+            p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+            if ( p_stream_id->p_sub_id != NULL )
+            {
+                sout_StreamIdDel( p_sys->p_out, p_stream_id->p_sub_id );
+                p_stream_id->p_sub_id = NULL;
+            }
+        }
+        vlc_array_clear( p_sys->p_streams_out );
+        sout_StreamChainDelete( p_sys->p_out, NULL );
+        p_sys->p_out = NULL;
+    }
+}
+
+static int StartPlayback( vlc_object_t *p_this, const char *psz_uri )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+    if ( p_sys->p_ping_ctx != NULL || p_sys->p_ping_thread != NULL )
+    {
+        assert( p_sys->p_ping_ctx != NULL && p_sys->p_ping_thread != NULL );
+        StopPlayback( p_this, false );
+        assert( p_sys->p_ping_ctx == NULL && p_sys->p_ping_thread == NULL );
+    }
+
+    vlc_dictionary_t keep_alive_headers;
+    vlc_dictionary_init( &keep_alive_headers, 1 );
+    vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+    // play
+    uint8_t *p_binary_plist = NULL;
+    uint32_t i_content_length;
+    plist_t p_plist = plist_new_dict();
+    plist_dict_set_item( p_plist, "Content-Location", plist_new_string( psz_uri ) );
+    plist_to_bin( p_plist, (char **)&p_binary_plist, &i_content_length );
+
+    int16_t i_status = DoHttp( p_this, &p_sys->i_authenticated_fd, "POST", "/play", "application/x-apple-binary-plist",
+                               (const char *)p_binary_plist, i_content_length, &keep_alive_headers, NULL, NULL, NULL );
+
+    if ( i_status != 200 )
+    {
+        msg_Err( p_this, "Could not start playback, status code: %d", i_status );
+        return VLC_EGENERIC;
+    }
+
+    vlc_dictionary_clear( &keep_alive_headers, NULL, NULL );
+
+    // init thread context
+    p_sys->p_ping_ctx = calloc( 1, sizeof( *p_sys->p_ping_ctx ) );
+    p_sys->p_ping_ctx->i_authenticated_fd = p_sys->i_authenticated_fd;
+    p_sys->p_ping_ctx->p_module = p_this;
+    vlc_mutex_init( &p_sys->p_ping_ctx->lock );
+
+    // init thread and clone
+    p_sys->p_ping_thread = calloc( 1, sizeof( *p_sys->p_ping_thread ) );
+    return vlc_clone( p_sys->p_ping_thread, PingThread, p_sys->p_ping_ctx, 1 );
+}
+
+static int StopPlayback( vlc_object_t *p_this, bool close_connection )
+{
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+    if ( p_sys->p_ping_ctx == NULL || &p_sys->p_ping_thread == NULL ) return VLC_EGENERIC;
+
+    vlc_mutex_lock( &p_sys->p_ping_ctx->lock );
+    p_sys->p_ping_ctx->b_join = true;
+    vlc_mutex_unlock( &p_sys->p_ping_ctx->lock );
+    vlc_join( *p_sys->p_ping_thread, NULL );
+
+    vlc_dictionary_t keep_alive_headers;
+    vlc_dictionary_init( &keep_alive_headers, 1 );
+    vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+    vlc_mutex_lock( &p_sys->p_ping_ctx->lock );
+    DoHttp( p_this, &p_sys->p_ping_ctx->i_authenticated_fd, "POST", "/stop",
+            NULL, NULL, 0, &keep_alive_headers, NULL, NULL, NULL );
+    vlc_mutex_unlock( &p_sys->p_ping_ctx->lock );
+
+    free( p_sys->p_ping_thread );
+    p_sys->p_ping_thread = NULL;
+    vlc_mutex_destroy( &p_sys->p_ping_ctx->lock );
+    free( p_sys->p_ping_ctx );
+    p_sys->p_ping_ctx = NULL;
+
+    if ( close_connection )
+    {
+        msg_Dbg( p_this, "no more outputs, closing connection" );
+        net_Close( p_sys->i_authenticated_fd );
+        p_sys->i_authenticated_fd = 0;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static void *PingThread( void *obj )
+{
+    ping_ctx_t *ctx = (ping_ctx_t *)obj;
+    uint64_t counter = 0;
+
+    while ( true )
+    {
+        vlc_mutex_lock( &ctx->lock );
+        if ( ctx->b_join )
+        {
+            vlc_mutex_unlock( &ctx->lock );
+            break;
+        }
+
+        if ( counter % 300 == 0 )
+        {
+            DoHttp( ctx->p_module, &ctx->i_authenticated_fd, "GET", "/info", NULL, NULL, 0, NULL, NULL, NULL, NULL );
+        }
+
+        vlc_mutex_unlock( &ctx->lock );
+        vlc_tick_sleep( 100000 );
+        counter += 1;
+    }
+
+    return (void *)VLC_SUCCESS;
+}
+
+static int m3u8Callback( httpd_file_sys_t *p_args,
+                         httpd_file_t *f, uint8_t const *p_request,
+                         uint8_t **pp_data, int *pi_data )
+{
+    VLC_UNUSED( f );
+    VLC_UNUSED( p_request );
+    sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_args;
+
+    vlc_mutex_lock( &p_sys->lock_m3u8 );
+    if ( p_sys->psz_m3u8 != NULL )
+    {
+        *pi_data = strlen( p_sys->psz_m3u8 );
+        *pp_data = malloc( sizeof( **pp_data ) * *pi_data );
+        memcpy( *pp_data, p_sys->psz_m3u8, *pi_data );
+    } else
+    {
+        *pp_data = NULL;
+        *pi_data = 0;
+    }
+    vlc_mutex_unlock( &p_sys->lock_m3u8 );
+
+    return VLC_SUCCESS;
+}
+
+static inline bool HasFeature( server_info_t *p_info, uint64_t i_feature )
+{
+    return p_info == NULL ? false : ( p_info->i_features & i_feature ) > 0;
+}
+
+static void FreeServerInfo( server_info_t **pp_info )
+{
+    if ( pp_info == NULL ) return;
+    free( ( *pp_info )->psz_deviceid );
+    free( ( *pp_info )->psz_model );
+    free( *pp_info );
+    *pp_info = NULL;
+}
+
+static void FreeSys( sout_stream_sys_t *p_sys )
+{
+    if ( p_sys == NULL ) return;
+    if ( p_sys->psz_bonjour_hostname != NULL ) free( p_sys->psz_bonjour_hostname );
+    if ( p_sys->psz_ipv4_ip != NULL ) free( p_sys->psz_ipv4_ip );
+    if ( p_sys->p_sexp_eddsa_keypair != NULL ) gcry_sexp_release( *p_sys->p_sexp_eddsa_keypair );
+    if ( p_sys->p_keystore != NULL ) vlc_keystore_release( p_sys->p_keystore );
+    if ( p_sys->p_server_info != NULL ) FreeServerInfo( &p_sys->p_server_info );
+    if ( p_sys->psz_m3u8 != NULL ) free( p_sys->psz_m3u8 );
+    if ( p_sys->p_m3u8_file != NULL ) httpd_FileDelete( p_sys->p_m3u8_file );
+    if ( p_sys->p_httpd_host != NULL ) httpd_HostDelete( p_sys->p_httpd_host );
+    if ( p_sys->p_streams_out != NULL ) vlc_array_clear( p_sys->p_streams_out );
+    if ( p_sys->p_streams != NULL ) vlc_array_clear( p_sys->p_streams );
+}
+
+static int64_t vlc_strtoi( const char *str )
+{
+    char *end;
+    long long l;
+
+    errno = 0;
+    l = strtoll( str, &end, 0 );
+
+    if ( !errno )
+    {
+#if ( LLONG_MAX > 0x7fffffffffffffffLL )
+        if (l > 0x7fffffffffffffffLL
+         || l < -0x8000000000000000LL)
+            errno = ERANGE;
+#endif
+        if ( *end )
+            errno = EINVAL;
+    }
+    return l;
+}
+
+static void ReverseBuffer( uint8_t *buffer, size_t length )
+{
+    uint8_t tmp;
+    for ( size_t i = 0; i < length / 2; i++ )
+    {
+        tmp = buffer[i];
+        buffer[i] = buffer[length - 1 - i];
+        buffer[length - 1 - i] = tmp;
+    }
+}
+
+static bool PortIsOpen( vlc_object_t *p_this, char *psz_host, uint16_t i_port )
+{
+    int *p_fd = net_ListenTCP( p_this, psz_host, i_port );
+    if ( p_fd == NULL )
+    {
+        return false;
+    } else
+    {
+        net_Close( *p_fd );
+        free( p_fd );
+        return true;
+    }
+}
+
+/*****************************************************************************
+ * Cryptography
+ *****************************************************************************/
+
+static int GetOrCreateEd25519( vlc_object_t *p_this, gcry_sexp_t **pp_sexp_ed )
+{
+    gcry_error_t gcry_error = 0;
+
+    uint8_t *p_bytes_ed_pub = NULL;
+    size_t i_length_ed_pub = 0;
+
+    uint8_t *p_bytes_ed_priv = NULL;
+    size_t i_length_ed_priv = 0;
+
+#if 0
+    HexToBytes( HARDCODED_ED_PUBLIC, &p_bytes_ed_pub, &i_length_ed_pub );
+    HexToBytes( HARDCODED_ED_PRIVATE, &p_bytes_ed_priv, &i_length_ed_priv );
+#else
+    sout_stream_t *p_stream = (sout_stream_t *)p_this;
+    sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+    vlc_keystore_entry *entries;
+    char *ppsz_values[KEY_MAX];
+    VLC_KEYSTORE_VALUES_INIT( ppsz_values );
+    ppsz_values[KEY_PROTOCOL] = AIRPLAY_USERNAME;
+    ppsz_values[KEY_SERVER] = p_sys->p_server_info->psz_deviceid;
+    unsigned int entries_count = vlc_keystore_find( p_sys->p_keystore, ppsz_values, &entries );
+
+    if ( entries_count < 1 )
+    {
+        gcry_sexp_t sexp_genkey_params = NULL;
+        gcry_sexp_t p_eddsa_keypair = NULL;
+
+        // need to generate key and store it
+        msg_Dbg( p_this, "Could not find Airplay credentials, creating now." );
+
+        if ( ( gcry_error = gcry_sexp_build( &sexp_genkey_params, NULL,
+                                             "(genkey (ecdsa (curve \"Ed25519\")"
+                                             "(flags eddsa)))" ) ) )
+        {
+            msg_Err( p_this, "eddsa params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+            return VLC_EGENERIC;
+        }
+
+        if ( ( gcry_error = gcry_pk_genkey( &p_eddsa_keypair, sexp_genkey_params ) ) )
+        {
+            msg_Err( p_this, "eddsa keygen failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+            return VLC_EGENERIC;
+        }
+
+        gcry_sexp_release( sexp_genkey_params );
+
+        gcry_mpi_t mpi_ed_pub;
+        gcry_mpi_t mpi_ed_priv;
+        gcry_error = gcry_sexp_extract_param( p_eddsa_keypair, NULL, "qd", &mpi_ed_pub,
+                                              &mpi_ed_priv, NULL );
+
+        if ( gcry_error )
+        {
+            msg_Err( p_this, "param extract failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+            return VLC_EGENERIC;
+        }
+
+        ppsz_values[KEY_USER] = "USER";
+
+        size_t i_length_ed_pub_priv = 64;
+        uint8_t *p_bytes_ed_pub_priv = malloc( i_length_ed_pub_priv * sizeof( *p_bytes_ed_pub_priv ) );
+        gcry_error = gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes_ed_pub_priv, i_length_ed_pub_priv / 2, NULL, mpi_ed_pub );
+        gcry_error = gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes_ed_pub_priv + i_length_ed_pub_priv / 2,
+                                     i_length_ed_pub_priv / 2, NULL, mpi_ed_priv );
+        char *psz_b64_ed_pub_priv = vlc_b64_encode_binary( p_bytes_ed_pub_priv, i_length_ed_pub_priv );
+
+        vlc_keystore_store( p_sys->p_keystore, ppsz_values, (const uint8_t *)psz_b64_ed_pub_priv, -1,
+                            "Airplay Credentials" );
+        entries_count = vlc_keystore_find( p_sys->p_keystore, ppsz_values, &entries );
+
+        free( p_bytes_ed_pub_priv );
+        free( psz_b64_ed_pub_priv );
+    }
+
+    vlc_keystore_entry entry = entries[0];
+
+    // if user or secret are unexpected, regenerate
+    if ( entry.ppsz_values[KEY_USER] == NULL ||
+         entry.i_secret_len == 0 )
+    {
+        msg_Err( p_this, "Invalid Configuration, Regenerating" );
+        vlc_keystore_remove( p_sys->p_keystore, ppsz_values );
+        return GetOrCreateEd25519( p_this, pp_sexp_ed );
+    }
+
+    uint8_t *p_bytes_ed_pub_priv = NULL;
+    size_t i_length_ed_pub_priv = vlc_b64_decode_binary( &p_bytes_ed_pub_priv, (const char *)entry.p_secret );
+    i_length_ed_pub = i_length_ed_pub_priv / 2;
+    i_length_ed_priv = i_length_ed_pub_priv / 2;
+    p_bytes_ed_pub = malloc( i_length_ed_pub );
+    p_bytes_ed_priv = malloc( i_length_ed_priv );
+    memcpy( p_bytes_ed_pub, p_bytes_ed_pub_priv, i_length_ed_pub_priv / 2 );
+    memcpy( p_bytes_ed_priv, p_bytes_ed_pub_priv + i_length_ed_pub_priv / 2, i_length_ed_pub_priv / 2 );
+    free( p_bytes_ed_pub_priv );
+#endif
+
+    gcry_sexp_t *p_eddsa_keypair = calloc( 1, sizeof( p_eddsa_keypair ) );
+
+    gcry_error = gcry_sexp_build( p_eddsa_keypair, NULL,
+                                  "("
+                                  "  key-data"
+                                  "  (public-key"
+                                  "    (ecc"
+                                  "      (curve \"Ed25519\")"
+                                  "      (flags eddsa)"
+                                  "      (q%b)"
+                                  "    )"
+                                  "  )"
+                                  "  (private-key"
+                                  "    (ecc"
+                                  "      (curve \"Ed25519\")"
+                                  "      (flags eddsa)"
+                                  "      (q%b)"
+                                  "      (d%b)"
+                                  "    )"
+                                  "  )"
+                                  ")", i_length_ed_pub, p_bytes_ed_pub, i_length_ed_pub, p_bytes_ed_pub,
+                                  i_length_ed_priv, p_bytes_ed_priv );
+
+    if ( gcry_error )
+    {
+        msg_Err( p_this, "Ed25519 key build failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+        return VLC_EGENERIC;
+    }
+
+    free( p_bytes_ed_priv );
+    free( p_bytes_ed_pub );
+
+    *pp_sexp_ed = p_eddsa_keypair;
+    return VLC_SUCCESS;
+}
+
+static int CreateCurve25519( vlc_object_t *p_this, gcry_sexp_t *p_sexp_curve )
+{
+    gcry_error_t gcry_error;
+
+#if 0
+    uint8_t *p_bytes_curve_public = NULL;
+    size_t i_length_curve_public;
+    HexToBytes( HARDCODED_CURVE_PUBLIC, &p_bytes_curve_public, &i_length_curve_public );
+
+    uint8_t *p_bytes_curve_private = NULL;
+    size_t i_length_curve_private;
+    HexToBytes( HARDCODED_CURVE_PRIVATE, &p_bytes_curve_private, &i_length_curve_private );
+
+    gcry_error = gcry_sexp_build( p_sexp_curve, NULL,
+                                  "("
+                                  "  key-data"
+                                  "  (public-key"
+                                  "    (ecc"
+                                  "      (curve \"Curve25519\")"
+                                  "      (flags djb-tweak)"
+                                  "      (q%b)"
+                                  "    )"
+                                  "  )"
+                                  "  (private-key"
+                                  "    (ecc"
+                                  "      (curve \"Curve25519\")"
+                                  "      (flags djb-tweak)"
+                                  "      (q%b)"
+                                  "      (d%b)"
+                                  "    )"
+                                  "  )"
+                                  ")",
+                                  i_length_curve_public, p_bytes_curve_public,
+                                  i_length_curve_public, p_bytes_curve_public,
+                                  i_length_curve_private, p_bytes_curve_private );
+
+    if ( gcry_error )
+    {
+        msg_Err( p_this, "Curve25519 key build failed: %s/%s", gcry_strsource( gcry_error ),
+                 gcry_strerror( gcry_error ) );
+        return VLC_EGENERIC;
+    }
+
+    free( p_bytes_curve_public );
+    free( p_bytes_curve_private );
+#else
+    gcry_sexp_t sexp_genkey_params;
+
+    if ( ( gcry_error = gcry_sexp_build( &sexp_genkey_params, NULL,
+                                         "(genkey"
+                                         "    (ecc"
+                                         "        (curve \"Curve25519\")"
+                                         "        (flags djb-tweak)"
+                                         "    )"
+                                         ")" ) ) )
+    {
+        msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+        return VLC_EGENERIC;
+    }
+
+    if ( ( gcry_error = gcry_pk_genkey( p_sexp_curve, sexp_genkey_params ) ) )
+    {
+        msg_Err( p_this, "curve generation failed: %s/%s", gcry_strsource( gcry_error ),
+                 gcry_strerror( gcry_error ) );
+        return VLC_EGENERIC;
+    }
+
+    gcry_sexp_release( sexp_genkey_params );
+#endif
+    return VLC_SUCCESS;
+}
+
+static void EncryptEd25519( const struct srp_user *p_user, uint8_t *p_bytes_ed25519, size_t i_length_ed25519,
+                            uint8_t **p_bytes_epk, uint8_t **p_bytes_authTag )
+{
+
+    uint8_t *p_bytes_aes_key;
+    uint8_t *p_bytes_aes_IV;
+    size_t i_length_aes_key_IV;
+    const uint8_t i_length_authTag = 16; // length is 16 for GCM mode
+
+    CalculatePairAESKeyIV( p_user, &p_bytes_aes_key, &p_bytes_aes_IV, &i_length_aes_key_IV );
+
+    *p_bytes_epk = calloc( i_length_ed25519, sizeof( **p_bytes_epk ) );
+    *p_bytes_authTag = calloc( i_length_authTag, sizeof( **p_bytes_authTag ) );
+
+    gcry_cipher_hd_t aes_handle;
+    gcry_cipher_open( &aes_handle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0 );
+    gcry_cipher_setkey( aes_handle, p_bytes_aes_key, i_length_aes_key_IV );
+    gcry_cipher_setiv( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+
+    gcry_cipher_encrypt( aes_handle, *p_bytes_epk, i_length_ed25519, p_bytes_ed25519, i_length_ed25519 );
+    gcry_cipher_gettag( aes_handle, *p_bytes_authTag, i_length_authTag );
+
+    gcry_cipher_close( aes_handle );
+}
+
+static void CalculatePairAESKeyIV( const struct srp_user *p_user, uint8_t **p_bytes_aes_key,
+                                   uint8_t **p_bytes_aes_IV, size_t *p_i_length_aes_key_IV )
+{
+    size_t i_length_K;
+    uint8_t *p_bytes_K;
+    *p_i_length_aes_key_IV = 16;
+    srp_user_get_hashed_session_key( p_user, &p_bytes_K, &i_length_K );
+
+    ( *p_bytes_aes_key ) = calloc( *p_i_length_aes_key_IV, sizeof( *( *p_bytes_aes_key ) ) );
+    ( *p_bytes_aes_IV ) = calloc( *p_i_length_aes_key_IV, sizeof( *( *p_bytes_aes_IV ) ) );
+
+    gcry_md_hd_t digest_handle_key;
+    gcry_md_open( &digest_handle_key, GCRY_MD_SHA512, 0 );
+    gcry_md_write( digest_handle_key, "Pair-Setup-AES-Key", sizeof( "Pair-Setup-AES-Key" ) - 1 );
+    gcry_md_write( digest_handle_key, p_bytes_K, i_length_K );
+    memcpy( ( *p_bytes_aes_key ), gcry_md_read( digest_handle_key, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+    gcry_md_close( digest_handle_key );
+
+    gcry_md_hd_t digest_handle_IV;
+    gcry_md_open( &digest_handle_IV, GCRY_MD_SHA512, 0 );
+    gcry_md_write( digest_handle_IV, "Pair-Setup-AES-IV", sizeof( "Pair-Setup-AES-IV" ) - 1 );
+    gcry_md_write( digest_handle_IV, p_bytes_K, i_length_K );
+    memcpy( ( *p_bytes_aes_IV ), gcry_md_read( digest_handle_IV, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+    ( *p_bytes_aes_IV )[*p_i_length_aes_key_IV - 1] += 1; // last byte incremented by 1
+    gcry_md_close( digest_handle_IV );
+    free( p_bytes_K );
+}
+
+static void EdSignConcatenatedBytes( gcry_sexp_t sexp_ed_keypair,
+                                     const uint8_t *p_bytes_a, const size_t i_length_a,
+                                     const uint8_t *p_bytes_b, const size_t i_length_b,
+                                     uint8_t **p_bytes_ed_signature, size_t *i_length_ed_signature )
+{
+    gcry_error_t gcry_error;
+
+    uint8_t *p_bytes_concat = calloc( i_length_a + i_length_b, sizeof( *p_bytes_concat ) );
+    memcpy( p_bytes_concat, p_bytes_a, i_length_a );
+    memcpy( p_bytes_concat + i_length_a, p_bytes_b, i_length_b );
+
+    gcry_sexp_t sexp_message = NULL;
+    if ( ( gcry_error = gcry_sexp_build( &sexp_message, NULL,
+                                         "(data"
+                                         " (flags eddsa)"
+                                         " (hash-algo sha512)"
+                                         " (value %b))",
+                                         i_length_a + i_length_b, p_bytes_concat ) ) )
+    {
+        // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+    }
+
+    free( p_bytes_concat );
+
+    gcry_sexp_t pub_key = gcry_sexp_find_token( sexp_ed_keypair, "public-key", 0 );
+    gcry_sexp_t sec_key = gcry_sexp_find_token( sexp_ed_keypair, "private-key", 0 );
+
+    gcry_sexp_t sexp_signature = NULL;
+    if ( ( gcry_error = gcry_pk_sign( &sexp_signature, sexp_message, sec_key ) ) )
+    {
+        // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+    }
+
+    if ( ( gcry_error = gcry_pk_verify( sexp_signature, sexp_message, pub_key ) ) )
+    {
+        // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+    }
+
+    gcry_sexp_release( sexp_message );
+    gcry_sexp_release( pub_key );
+    gcry_sexp_release( sec_key );
+
+    gcry_mpi_t mpi_r = NULL;
+    gcry_mpi_t mpi_s = NULL;
+    if ( ( gcry_error = gcry_sexp_extract_param( sexp_signature, NULL, "rs", &mpi_r, &mpi_s, NULL ) ) )
+    {
+        // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+    }
+
+    gcry_sexp_release( sexp_signature );
+
+    uint8_t *p_bytes_r = NULL;
+    size_t i_length_r;
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_r, &i_length_r, mpi_r );
+
+    uint8_t *p_bytes_s = NULL;
+    size_t i_length_s;
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_s, &i_length_s, mpi_s );
+
+    *i_length_ed_signature = i_length_r + i_length_s;
+    *p_bytes_ed_signature = calloc( *i_length_ed_signature, sizeof( **p_bytes_ed_signature ) );
+
+    gcry_mpi_release( mpi_r );
+    gcry_mpi_release( mpi_s );
+
+    // R ~ S
+    memcpy( *p_bytes_ed_signature, p_bytes_r, i_length_r );
+    memcpy( *p_bytes_ed_signature + i_length_r, p_bytes_s, i_length_s );
+}
+
+static gcry_error_t Curve25519DiffieHellman( const uint8_t *p_bytes_a, size_t i_length_a, const uint8_t *p_bytes_B,
+                                             size_t i_length_B, uint8_t **pp_bytes_shared, size_t *p_i_length_shared )
+{
+    gcry_error_t gcry_error = 0;
+    uint8_t *p_bytes_a_reversed = calloc( i_length_a, sizeof( *p_bytes_a_reversed ) );
+    gcry_sexp_t sexp_a = NULL;
+    gcry_sexp_t sexp_B = NULL;
+    gcry_sexp_t sexp_shared_key = NULL;
+
+    gcry_mpi_t mpi_curve_shared_compressed = NULL;
+    gcry_mpi_t mpi_curve_shared = gcry_mpi_new( 0 );
+
+    memcpy( p_bytes_a_reversed, p_bytes_a, i_length_a );
+    ReverseBuffer( p_bytes_a_reversed, i_length_a );
+
+    if (
+        ( gcry_error = gcry_sexp_build( &sexp_a, NULL, "%b", i_length_a, p_bytes_a_reversed ) ) ||
+        ( gcry_error = gcry_sexp_build( &sexp_B, NULL,
+                                        "(public-key"
+                                        " (ecc"
+                                        "  (curve \"Curve25519\")"
+                                        "  (flags djb-tweak)"
+                                        "  (q%b)))", i_length_B, p_bytes_B ) ) ||
+        ( gcry_error = gcry_pk_encrypt( &sexp_shared_key, sexp_a, sexp_B ) ) )
+    {
+        goto end;
+    }
+
+    // the shared key is a point stored compressed
+    // in an mpi and it will need to be decompressed
+    gcry_sexp_extract_param( sexp_shared_key, NULL, "s", &mpi_curve_shared_compressed, NULL );
+    if ( ( gcry_error = CurveDecompress( mpi_curve_shared_compressed, &mpi_curve_shared ) ) )
+    {
+        goto end;
+    }
+
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, pp_bytes_shared, p_i_length_shared, mpi_curve_shared );
+    ReverseBuffer( *pp_bytes_shared, *p_i_length_shared );
+
+    end:
+    free( p_bytes_a_reversed );
+    if ( sexp_a != NULL )
+        gcry_sexp_release( sexp_a );
+    if ( sexp_B != NULL )
+        gcry_sexp_release( sexp_B );
+    if ( mpi_curve_shared_compressed != NULL )
+        gcry_mpi_release( mpi_curve_shared_compressed );
+
+    gcry_mpi_release( mpi_curve_shared );
+    return gcry_error;
+}
+
+static gcry_error_t CurveDecompress( gcry_mpi_t mpi_compressed, gcry_mpi_t *mpi_uncompressed )
+{
+    gcry_error_t gcry_error;
+
+    // to decompress, we decode it into a point
+    // then extract the X and discard the rest
+    gcry_ctx_t ctx_curve;
+    if ( ( gcry_error = gcry_mpi_ec_new( &ctx_curve, NULL, "Curve25519" ) ) )
+    {
+        return gcry_error;
+    }
+
+    gcry_mpi_point_t point_curve_shared = gcry_mpi_point_new( 0 );
+    if ( ( gcry_error = gcry_mpi_ec_decode_point( point_curve_shared, mpi_compressed, ctx_curve ) ) )
+    {
+        return gcry_error;
+    }
+
+    // we extract x, y and z but only need x because
+    // curve only uses the x coordinate.
+    gcry_mpi_t mpi_curve_shared_y = gcry_mpi_new( 0 );
+    gcry_mpi_t mpi_curve_shared_z = gcry_mpi_new( 0 );
+
+    gcry_mpi_point_snatch_get( *mpi_uncompressed, mpi_curve_shared_y,
+                               mpi_curve_shared_z, point_curve_shared );
+    gcry_mpi_release( mpi_curve_shared_y );
+    gcry_mpi_release( mpi_curve_shared_z );
+
+    return 0;
+}
+
+static gcry_error_t CalculateAESSignature( const uint8_t *p_bytes_atv_challenge, size_t i_length_atv_challenge,
+                                           const uint8_t *p_bytes_ed_signature, size_t i_length_ed_signature,
+                                           const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+                                           uint8_t **pp_bytes_aes_signature, size_t *p_i_length_aes_signature )
+{
+    gcry_error_t gcry_error;
+
+    uint8_t *p_bytes_aes_key;
+    uint8_t *p_bytes_aes_IV;
+    size_t i_length_aes_key_IV;
+
+    CalculateVerifyAESKeyIV( p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_key, &p_bytes_aes_IV,
+                             &i_length_aes_key_IV );
+
+    gcry_cipher_hd_t aes_handle;
+    gcry_cipher_open( &aes_handle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0 );
+    gcry_cipher_setkey( aes_handle, p_bytes_aes_key, i_length_aes_key_IV );
+    gcry_cipher_setiv( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+    gcry_cipher_setctr( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+
+    *p_i_length_aes_signature = 64;
+    *pp_bytes_aes_signature = calloc( *p_i_length_aes_signature, sizeof( **pp_bytes_aes_signature ) );
+
+    // encrypt atv_data and discard then encrypt p_bytes_ed_signature so that the sig is correct
+    if ( ( gcry_error = gcry_cipher_encrypt( aes_handle, *pp_bytes_aes_signature, *p_i_length_aes_signature,
+                                             p_bytes_atv_challenge, i_length_atv_challenge ) ) )
+    {
+        return gcry_error;
+    }
+    if ( ( gcry_error = gcry_cipher_encrypt( aes_handle, *pp_bytes_aes_signature, *p_i_length_aes_signature,
+                                             p_bytes_ed_signature, i_length_ed_signature ) ) )
+    {
+        return gcry_error;
+    }
+
+    gcry_cipher_close( aes_handle );
+    free( p_bytes_aes_IV );
+    free( p_bytes_aes_key );
+
+    return 0;
+}
+
+static void CalculateVerifyAESKeyIV( const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+                                     uint8_t **p_bytes_aes_key, uint8_t **p_bytes_aes_IV,
+                                     size_t *p_i_length_aes_key_IV )
+{
+    *p_i_length_aes_key_IV = 16;
+
+    gcry_md_hd_t digest_handle_key;
+    gcry_md_open( &digest_handle_key, GCRY_MD_SHA512, 0 );
+    gcry_md_write( digest_handle_key, "Pair-Verify-AES-Key", strlen( "Pair-Verify-AES-Key" ) );
+    gcry_md_write( digest_handle_key, p_bytes_curve_shared, i_length_curve_shared );
+
+    gcry_md_hd_t digest_handle_IV;
+    gcry_md_open( &digest_handle_IV, GCRY_MD_SHA512, 0 );
+    gcry_md_write( digest_handle_IV, "Pair-Verify-AES-IV", strlen( "Pair-Verify-AES-IV" ) );
+    gcry_md_write( digest_handle_IV, p_bytes_curve_shared, i_length_curve_shared );
+
+    *p_bytes_aes_key = calloc( *p_i_length_aes_key_IV, sizeof( **p_bytes_aes_key ) );
+    *p_bytes_aes_IV = calloc( *p_i_length_aes_key_IV, sizeof( **p_bytes_aes_IV ) );
+
+    memcpy( *p_bytes_aes_key, gcry_md_read( digest_handle_key, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+    memcpy( *p_bytes_aes_IV, gcry_md_read( digest_handle_IV, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+
+    gcry_md_close( digest_handle_key );
+    gcry_md_close( digest_handle_IV );
+}
+
+#ifdef AIRPLAY_TEST
+
+static void HexToBytes( const char *psz_hex, uint8_t **p_bytes, size_t *i_length )
+{
+    gcry_mpi_t mpi = gcry_mpi_new( 0 );
+    gcry_mpi_scan( &mpi, GCRYMPI_FMT_HEX, psz_hex, 0, NULL );
+    gcry_mpi_aprint( GCRYMPI_FMT_USG, p_bytes, i_length, mpi );
+}
+
+static bool TestCrypto()
+{
+    bool pass = true;
+
+    if ( !TestPair( "5QO6BSB38B2XKFDI", "1364", "550a86d201376674ee8ce46ef472db01",
+                    "1995b7a4f0d364de3a57274f62e635b7c8bdd3123063e3273dbf26a8e2e7d03a93a037af9431dae8d11fa293e4a6e167b2"
+                    "3709f313efe3ae1a6c6b58066f823fff8293c613c6447bf787b7db6eae9aeb69316fc40edeb8847b76c89a62f7820a762b"
+                    "e97fd8ddb503663ae63e538c1b6ee80cc8faa6a678729b9f0391514c4f9ba524baef1aeb887f847fe884b729cb305f6e78"
+                    "4b08c318f29c09bffb97281cbff7c3099a235464c9ec8f471117c0e9bf9f870b2e1de0a21e949fc3015d66953b983e484e"
+                    "451087b315f771147e273408e4bad42341c5786290454c89ffce77915002fd5b8ce4c1c13b039081f79749426c5da7d889"
+                    "c05b60d5952938af491854",
+                    "45f30247f402972ba648f65592434026bc09754e5069dd311b198d6eba0f9b4ec1b4bcf7c618a4761845a6b9d591458b7c"
+                    "aaf453466aa72185718a0f961b8171e4eb28d5312d610c85aa7eff3607a0329cf8073788ca707da80c5a7776185001587d"
+                    "f44ef9973cacd7872b291ed8607df576f2c286d776d88fe5281362daca2328de9c7f4a0729dcb2e835b28b8323e6383044"
+                    "65ba09ee3fbf16f42624a15b660a3d2a40dfc4e96df9a10ed9ce3cca380189584543bbbd46428489fbadccaf47791c51da"
+                    "4aa928b496e4b0fd3b1eec42b47740cc9b6c9bdfe968fb74c1eb014e33b2d6b7f4c43e97f24b751ce2364c042eff678cba"
+                    "70f9945190e42e9cf78421",
+                    "7eb98a04ddb068e99a11c374081e6ea63231841a45e591890217842e880f8beb7a7c0505ac796c58",
+                    "2bc212e99d4cb440ed318827c1348fc05120bfe7", "aabe40b9027e3a0cbaaa8a751ac335bd0273b577",
+                    "62b360b740800423e348996afbda4425", "12208dc4cd054ec1bb657ee48fc9ffb4",
+                    "a8562f18c1d7b6294b9cfb5f37602e2f95bf6c0a1ec835b78e051886b32a2043",
+                    "90c867c8f8f55dc37b25fcd20be27e4f549df0f120422312a91e0f4afda8d51d",
+                    "eacbf4ba605b9ef7918b513f0e007223" ) )
+    {
+        printf( "Pair not correct" );
+        pass = false;
+    }
+
+    if ( !TestVerify( "e65aa4bd200d7fb26e7c64803a6cefdab868c09b716fb9a922b8bb5e7ec9102b",
+                      "beadc555fabb47d3fad99ed1f0c11f803730a1f27ca8360804a49466c12460a1",
+                      "122894d5bd9e38e0bb9e47a29657bdf4ef6a410c901ea4c50f501c66d21690da3df482c712446cf432415274220b6a5008834d70f3c7efcaa5f0a80ebb888cda",
+                      "687e235342da53b413de4ab8f64ef794fa1b67e43d52701fd16ab962ef35356f",
+                      "f6739cba89bd5e644b113ff56c2ca399e0e2f08a6bca83f03ccd6b90a5dfd841",
+                      "78f8f68c6da6c66f4b9b893d9ba03b31594e2dd38026f65d7b7b8953ecb6717c",
+                      "b7401d839100cee68fc995e43a5e2df9406bf6df8d209f62ebe5783f109f760",
+                      "697c6a74bca8c8f37775e50e9e2b0e93", "8a4fd50e8e2ceefb25f9fc2594b2027b",
+                      "bdcbb15a8b7e08888c3370a2a3f8ca1e9292bc8d3c18547b4ee0ba3ef834d8e7315f665f5e04f481fac923567de27c56800c72862b00ea367bc83b5b6269ca09",
+                      "49239fd6879f6c1a72fe8949e225d1dcf5c0861f81e8f9b70b4f9a6ab480acb0b263454194747c726f2679e81b9a5a1f0b210a0d3c609073f84895dc88c03b80" ) )
+    {
+        printf( "Verify not correct" );
+        pass = false;
+    }
+
+    if ( !TestVerify( "766ad48e3de029d548be8da9f1de10bee4af2b8e85414b801d5c19b976d494e8",
+                      "30553b3a95eb3569124a58786909559f8002f69d6c412cb68c43078e4f88e6fd",
+                      "6ca48af0ff662349123bab2d97f118e8cebb12bb9cb6370c08bfa78a7d5c3d8ab8c8481618932ea6653f5f3005fdc9555492a74230db2a700338a534e601db25",
+                      "d5406e72caa0c8b48ebce34d6243ea44613b5e3b3b6cec63b436b5e8b1735c04",
+                      "772e71554c5ea8a5592ac209af299ef9d4c767d223c160051d3b4d457e750509",
+                      "e0e65f15be4f1a5d0c3e74a33debdbb3f735170c298b967ab4211d05e9d30767",
+                      "d2ad4edc061cd76899794a34aca6a6e6b4d4c4db232896712b6f6b27c5a06c15",
+                      "17aec1b52efa336168e90ab4ed03662b",
+                      "7b007b405d383edc786e3059fc427541",
+                      "a8819bc494cad169b41254675e4edaca056c23b9b3557163c7007b5297b99596ef3a29997cf18d8129f170f6d6c8b2f45af6252f0c0f883059f5b8fbc46b003",
+                      "ab4d034f4438fc9c75a163f077e918d5dde130f68f1f334b3c472923474c97a0720223859b7b0794584d6e322b9741edfccb24a7a864b089be2806f623cd01a6" ) )
+    {
+        printf( "Verify not correct" );
+        pass = false;
+    }
+
+    return pass;
+}
+
+static bool TestPair( const char *psz_I, const char *psz_P, const char *psz_hex_s, const char *psz_hex_B,
+                      const char *psz_hex_a, const char *psz_hex_K, const char *psz_hex_M1, const char *psz_hex_M2,
+                      const char *psz_hex_aes_key, const char *psz_hex_aes_IV, const char *psz_hex_ed25519,
+                      const char *psz_hex_epk, const char *psz_hex_signature )
+{
+    bool pass = true;
+
+    struct srp_user *p_user = apple_srp_user_new( psz_I, strlen( psz_I ), psz_P, strlen( psz_P ) );
+
+    gcry_mpi_t mpi_a = gcry_mpi_new( 0 );
+    gcry_mpi_scan( &mpi_a, GCRYMPI_FMT_HEX, psz_hex_a, 0, NULL );
+
+    uint8_t *p_bytes_A = NULL;
+    size_t i_A_length = 0;
+    srp_user_start_authentication( p_user, &mpi_a, &p_bytes_A, &i_A_length );
+
+    uint8_t *p_bytes_s = NULL;
+    size_t i_length_s = 0;
+    uint8_t *p_bytes_B = NULL;
+    size_t i_length_B = 0;
+    HexToBytes( psz_hex_s, &p_bytes_s, &i_length_s );
+    HexToBytes( psz_hex_B, &p_bytes_B, &i_length_B );
+
+    uint8_t *p_bytes_M1 = NULL;
+    size_t i_length_M1 = 0;
+    srp_user_process_challenge( p_user, p_bytes_s, i_length_s, p_bytes_B, i_length_B, &p_bytes_M1, &i_length_M1 );
+
+    uint8_t *p_bytes_K;
+    size_t i_length_K;
+    srp_user_get_hashed_session_key( p_user, &p_bytes_K, &i_length_K );
+
+    uint8_t *p_bytes_K_Expected;
+    size_t i_length_K_Expected;
+    HexToBytes( psz_hex_K, &p_bytes_K_Expected, &i_length_K_Expected );
+
+    if ( i_length_K != i_length_K_Expected )
+    {
+        printf( "K lengths not equal." );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_K, p_bytes_K_Expected, i_length_K ) != 0 )
+    {
+        printf( "K content not equal." );
+        pass = false;
+    }
+
+    // assert M1 == M1Expected
+    uint8_t *p_bytes_M1_Expected;
+    size_t i_length_M1_Expected;
+    HexToBytes( psz_hex_M1, &p_bytes_M1_Expected, &i_length_M1_Expected );
+
+    if ( i_length_M1 != i_length_M1_Expected )
+    {
+        printf( "M1 lengths not equal." );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_M1, p_bytes_M1_Expected, i_length_M1 ) != 0 )
+    {
+        printf( "M1 content not equal." );
+        pass = false;
+    }
+
+    // verify M2
+    uint8_t *p_bytes_M2 = NULL;
+    size_t i_length_M2;
+    HexToBytes( psz_hex_M2, &p_bytes_M2, &i_length_M2 );
+
+    if ( !srp_user_verify_session( p_user, p_bytes_M2 ) )
+    {
+        printf( "M2 not valid" );
+        pass = false;
+    }
+
+    // generate AES key and IV
+    uint8_t *p_aes_key;
+    uint8_t *p_aes_IV;
+    size_t i_length_key_IV;
+    CalculatePairAESKeyIV( p_user, &p_aes_key, &p_aes_IV, &i_length_key_IV );
+
+    uint8_t *p_aes_key_Expected;
+    size_t i_aes_key_Expected;
+    HexToBytes( psz_hex_aes_key, &p_aes_key_Expected, &i_aes_key_Expected );
+
+    uint8_t *p_aes_IV_Expected;
+    size_t i_aes_IV_Expected;
+    HexToBytes( psz_hex_aes_IV, &p_aes_IV_Expected, &i_aes_IV_Expected );
+
+    if ( memcmp( p_aes_key, p_aes_key_Expected, i_length_key_IV ) != 0 )
+    {
+        printf( "AES Key content not equal." );
+        pass = false;
+    }
+
+    if ( memcmp( p_aes_IV, p_aes_IV_Expected, i_length_key_IV ) != 0 )
+    {
+        printf( "AES IV not equal." );
+        pass = false;
+    }
+
+    uint8_t *p_bytes_epk;
+    uint8_t *p_bytes_authTag;
+
+    uint8_t *p_bytes_ed25519;
+    size_t i_length_ed25519;
+    HexToBytes( psz_hex_ed25519, &p_bytes_ed25519, &i_length_ed25519 );
+
+    EncryptEd25519( p_user, p_bytes_ed25519, i_length_ed25519, &p_bytes_epk, &p_bytes_authTag );
+
+    uint8_t *p_bytes_epk_Expected;
+    HexToBytes( psz_hex_epk, &p_bytes_epk_Expected, NULL );
+
+    uint8_t *p_bytes_authTag_Expected;
+    HexToBytes( psz_hex_signature, &p_bytes_authTag_Expected, NULL );
+
+    if ( memcmp( p_bytes_epk, p_bytes_epk_Expected, i_length_key_IV ) != 0 )
+    {
+        printf( "epk not equal." );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_authTag, p_bytes_authTag_Expected, i_length_key_IV ) != 0 )
+    {
+        printf( "authTag not equal." );
+        pass = false;
+    }
+
+    return pass;
+}
+
+static bool TestVerify( const char *psz_hex_ed_priv, const char *psz_hex_ed_pub, const char *psz_hex_atv_challenge,
+                        const char *psz_hex_atv_curve_pub, const char *psz_hex_curve_pub,
+                        const char *psz_hex_curve_priv, const char *psz_hex_curve_shared, const char *psz_hex_aes_key,
+                        const char *psz_hex_aes_IV, const char *psz_hex_ed_signature,
+                        const char *psz_hex_aes_signature )
+{
+    bool pass = true;
+
+    // verify Curve25519 diffie helman
+
+    if ( !TestCurveDH( psz_hex_curve_priv, psz_hex_atv_curve_pub, psz_hex_curve_shared ) )
+    {
+        printf( "Curve incorrect" );
+        pass = false;
+    }
+
+    // verify aes IV and key
+
+    if ( !TestAESKeyIV( psz_hex_curve_shared, psz_hex_aes_key, psz_hex_aes_IV ) )
+    {
+        printf( "AES Key or IV incorrect" );
+        pass = false;
+    }
+
+    // test ED sign
+
+    if ( !TestEdSign( psz_hex_ed_priv, psz_hex_ed_pub, psz_hex_curve_pub, psz_hex_atv_curve_pub,
+                      psz_hex_ed_signature ) )
+    {
+        printf( "ED Signature Failed" );
+        pass = false;
+    }
+
+    // verify AES signature
+
+    if ( !TestAESSignature( psz_hex_atv_challenge, psz_hex_curve_shared, psz_hex_ed_signature, psz_hex_aes_signature ) )
+    {
+        printf( "AES Signature failed" );
+        pass = false;
+    };
+
+    return pass;
+}
+
+static bool TestAESSignature( const char *psz_hex_atv_challenge, const char *psz_hex_curve_shared,
+                              const char *psz_hex_ed_signature, const char *psz_hex_aes_signature )
+{
+    bool pass = true;
+
+    uint8_t *p_bytes_atv_challenge = NULL;
+    size_t i_length_atv_challenge;
+    HexToBytes( psz_hex_atv_challenge, &p_bytes_atv_challenge, &i_length_atv_challenge );
+
+    uint8_t *p_bytes_ed_signature = NULL;
+    size_t i_length_ed_signature;
+    HexToBytes( psz_hex_ed_signature, &p_bytes_ed_signature, &i_length_ed_signature );
+
+    uint8_t *p_bytes_curve_shared = NULL;
+    size_t i_length_curve_shared;
+    HexToBytes( psz_hex_curve_shared, &p_bytes_curve_shared, &i_length_curve_shared );
+
+    uint8_t *p_bytes_aes_signature = NULL;
+    size_t i_length_aes_signature;
+
+    CalculateAESSignature( p_bytes_atv_challenge, i_length_atv_challenge, p_bytes_ed_signature, i_length_ed_signature,
+                           p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_signature,
+                           &i_length_aes_signature );
+
+    uint8_t *p_bytes_aes_signature_expected = NULL;
+    size_t i_length_aes_signature_expected;
+    HexToBytes( psz_hex_aes_signature, &p_bytes_aes_signature_expected, &i_length_aes_signature_expected );
+
+    if ( i_length_aes_signature != i_length_aes_signature_expected )
+    {
+        printf( "AES Signature invalid length" );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_aes_signature, p_bytes_aes_signature_expected, i_length_aes_signature ) != 0 )
+    {
+        printf( "AES Signature not expected" );
+        pass = false;
+    }
+
+    free( p_bytes_aes_signature );
+    free( p_bytes_aes_signature_expected );
+    free( p_bytes_curve_shared );
+
+    return pass;
+}
+
+static bool TestAESKeyIV( const char *psz_hex_curve_shared, const char *psz_hex_aes_key, const char *psz_hex_aes_IV )
+{
+    bool pass = true;
+
+    uint8_t *p_bytes_curve_shared = NULL;
+    size_t i_length_curve_shared;
+    HexToBytes( psz_hex_curve_shared, &p_bytes_curve_shared, &i_length_curve_shared );
+
+    uint8_t *p_bytes_aes_key;
+    uint8_t *p_bytes_aes_IV;
+    size_t i_length_aes_key_IV;
+    CalculateVerifyAESKeyIV( p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_key, &p_bytes_aes_IV,
+                             &i_length_aes_key_IV );
+
+    uint8_t *p_bytes_aes_key_expected;
+    size_t i_length_aes_key_expected;
+    HexToBytes( psz_hex_aes_key, &p_bytes_aes_key_expected, &i_length_aes_key_expected );
+
+    uint8_t *p_bytes_aes_IV_expected;
+    size_t i_length_aes_IV_expected;
+    HexToBytes( psz_hex_aes_IV, &p_bytes_aes_IV_expected, &i_length_aes_IV_expected );
+
+    if ( i_length_aes_key_IV != i_length_aes_key_expected )
+    {
+        printf( "AES Key invalid length" );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_aes_key, p_bytes_aes_key_expected, i_length_aes_key_IV ) != 0 )
+    {
+        printf( "AES Key not expected" );
+        pass = false;
+    }
+
+    if ( i_length_aes_key_IV != i_length_aes_IV_expected )
+    {
+        printf( "AES IV invalid length" );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_aes_IV, p_bytes_aes_IV_expected, i_length_aes_key_IV ) != 0 )
+    {
+        printf( "AES IV not expected" );
+        pass = false;
+    }
+
+    free( p_bytes_curve_shared );
+    free( p_bytes_aes_IV );
+    free( p_bytes_aes_IV_expected );
+    free( p_bytes_aes_key );
+    free( p_bytes_aes_key_expected );
+
+    return pass;
+}
+
+static bool TestCurveDH( const char *psz_hex_a, const char *psz_hex_B, const char *psz_hex_shared )
+{
+    bool pass = true;
+
+    uint8_t *p_bytes_a = NULL;
+    size_t i_length_a;
+    HexToBytes( psz_hex_a, &p_bytes_a, &i_length_a );
+
+    uint8_t *p_bytes_B = NULL;
+    size_t i_length_B;
+    HexToBytes( psz_hex_B, &p_bytes_B, &i_length_B );
+
+    uint8_t *p_bytes_shared = NULL;
+    size_t i_length_shared;
+    Curve25519DiffieHellman( p_bytes_a, i_length_a, p_bytes_B, i_length_B, &p_bytes_shared, &i_length_shared );
+
+    uint8_t *p_bytes_shared_expected = NULL;
+    size_t i_length_shared_expected;
+    HexToBytes( psz_hex_shared, &p_bytes_shared_expected, &i_length_shared_expected );
+
+    if ( i_length_shared != i_length_shared_expected )
+    {
+        printf( "Curve invalid length" );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_shared, p_bytes_shared_expected, i_length_shared ) != 0 )
+    {
+        printf( "Curve not expected" );
+        pass = false;
+    }
+
+    return pass;
+}
+
+static bool TestEdSign( const char *psz_hex_ed_priv, const char *psz_hex_ed_pub, const char *psz_hex_curve_pub,
+                        const char *psz_hex_atv_curve_pub, const char *psz_hex_ed_signature )
+{
+    bool pass = true;
+    gcry_error_t gcry_error;
+
+    uint8_t *p_bytes_ed_priv;
+    size_t i_length_ed_priv;
+    HexToBytes( psz_hex_ed_priv, &p_bytes_ed_priv, &i_length_ed_priv );
+
+    uint8_t *p_bytes_ed_pub;
+    size_t i_length_ed_pub;
+    HexToBytes( psz_hex_ed_pub, &p_bytes_ed_pub, &i_length_ed_pub );
+
+    gcry_sexp_t sexp_ed = NULL;
+    gcry_error = gcry_sexp_build( &sexp_ed, NULL,
+                                  "("
+                                  "  key-data"
+                                  "  (public-key"
+                                  "    (ecc"
+                                  "      (curve \"Ed25519\")"
+                                  "      (flags eddsa)"
+                                  "      (q%b)"
+                                  "    )"
+                                  "  )"
+                                  "  (private-key"
+                                  "    (ecc"
+                                  "      (curve \"Ed25519\")"
+                                  "      (flags eddsa)"
+                                  "      (q%b)"
+                                  "      (d%b)"
+                                  "    )"
+                                  "  )"
+                                  ")",
+                                  i_length_ed_pub, p_bytes_ed_pub,
+                                  i_length_ed_pub, p_bytes_ed_pub,
+                                  i_length_ed_priv, p_bytes_ed_priv );
+
+    uint8_t *p_bytes_curve_pub;
+    size_t i_length_curve_pub;
+    HexToBytes( psz_hex_curve_pub, &p_bytes_curve_pub, &i_length_curve_pub );
+
+    uint8_t *p_bytes_atv_curve_pub;
+    size_t i_length_atv_curve_pub;
+    HexToBytes( psz_hex_atv_curve_pub, &p_bytes_atv_curve_pub, &i_length_atv_curve_pub );
+
+    uint8_t *p_bytes_ed_signature;
+    size_t i_length_ed_signature;
+    EdSignConcatenatedBytes( sexp_ed, p_bytes_curve_pub, i_length_curve_pub,
+                             p_bytes_atv_curve_pub, i_length_atv_curve_pub,
+                             &p_bytes_ed_signature, &i_length_ed_signature );
+
+    uint8_t *p_bytes_ed_signature_expected;
+    size_t i_length_ed_signature_expected;
+    HexToBytes( psz_hex_ed_signature, &p_bytes_ed_signature_expected, &i_length_ed_signature_expected );
+
+    if ( i_length_ed_signature != i_length_ed_signature_expected )
+    {
+        printf( "Ed Signature invalid length" );
+        pass = false;
+    }
+
+    if ( memcmp( p_bytes_ed_signature, p_bytes_ed_signature_expected, i_length_ed_signature ) != 0 )
+    {
+        printf( "Ed Signature not expected" );
+        pass = false;
+    }
+
+    return pass;
+}
+
+#endif
diff --git a/modules/stream_out/airplay/csrp/srp.c b/modules/stream_out/airplay/csrp/srp.c
new file mode 100755
index 0000000000..1527d35fc7
--- /dev/null
+++ b/modules/stream_out/airplay/csrp/srp.c
@@ -0,0 +1,955 @@
+/*
+ * A Secure Remote Password 6a implementation (RFC 5054)
+ * Also includes Apple-Flavoured SRP which changes a
+ * few of the functions to be compatible with Apple TVs.
+ *
+ * Adapted from CSRP by Tom Cocagne by Alexander Lyon.
+ * https://github.com/cocagne/csrp
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Tom Cocagne, Alexander Lyon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Below are the symbols used in the code:
+ *
+ * Symbol    | Description
+ * --------- | -----------
+ * H(x)      | One-way hash function (SHA1)
+ * PAD(x, y) | Left pad x to nearest byte, length y
+ * ^         | Exponentiation
+ * ~         | Concatenation
+ *
+ * Symbol | Source          | Calculation              | Description
+ * -- | ------------------  | ------------------------ | --------------------------------
+ * N  | Predetermined       |                          | A large safe prime (N = 2q+1, where q is prime)
+ * g  | Predetermined       |                          | A generator
+ * a  | Generated by Client |                          | Client ephemeral secret key
+ * b  | Generated by Server |                          | Server ephemeral secret key
+ * I  | Supplied by User    |                          | Username or Identity
+ * P  | Supplied by User    |                          | Cleartext Password
+ * s  | Generated by Server |                          | Salt
+ * k  | Calculated          | `H(PAD(N, N) ~ PAD(g, N))` | SRP-6 multiplier
+ * x  | Calculated          | `H(s ~ H( I ~ ':' ~ P ))`  | Used to calculate the verifier and shared session key
+ * v  | Calculated          | `g^x % N`                  | Server password verifier
+ * B  | Calculated by Server | `(k * v + g^b) % N`   | Server ephemeral public key
+ * A  | Calculated by Client | `g^a % N`             | Client ephemeral public key
+ * u  | Calculated           | `H(A ~ B)`            | Random scrambling parameter
+ * S<sub>Client</sub> | Calculated | `(B - k * g^x) ^ (a + u * x) % N` |
+ * S<sub>Server</sub> | Calculated | `(A * v^u) ^ b % N` |
+ * K  | Calculated  | `H(H(S) ~ 00000000) ~ H(H(S) ~ 00000001)` | Shared Key
+ * M1 | Calculated | `H(H(N) xor H(g) ~ H(I) ~ s ~ A ~ B ~ K)` | Client key verifier
+ * M2 | Calculated | `H(A ~ M1 ~ K)` | Server key verifier
+ */
+
+#include <assert.h>
+#include <gcrypt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "srp.h"
+
+typedef struct
+{
+    gcry_mpi_t N;
+    gcry_mpi_t g;
+} Ng_pair_t;
+
+typedef struct
+{
+    const char *psz_N;
+    const char *psz_g;
+} Ng_hex_pair_t;
+
+/* All constants here were pulled from Appendix A of RFC 5054 */
+static Ng_hex_pair_t Ng_hex_pairs[] = {
+    {
+        /* 1024 */
+        "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496"
+        "EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E"
+        "F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA"
+        "9AFD5138FE8376435B9FC61D2FC0EB06E3",
+        "2"
+    },
+    {
+        /* 2048 */
+        "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
+        "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
+        "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
+        "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
+        "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
+        "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
+        "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
+        "2"
+    },
+    {
+        /* 4096 */
+        "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+        "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+        "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+        "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+        "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+        "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+        "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+        "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+        "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+        "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+        "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+        "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+        "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+        "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+        "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+        "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+        "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+        "FFFFFFFFFFFFFFFF",
+        "5"
+    },
+    {
+        /* 8192 */
+        "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+        "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+        "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+        "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+        "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+        "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+        "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+        "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+        "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+        "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+        "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+        "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+        "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+        "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+        "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+        "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+        "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+        "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+        "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+        "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+        "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+        "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+        "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+        "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+        "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+        "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+        "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA"
+        "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C"
+        "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+        "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886"
+        "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6"
+        "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5"
+        "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268"
+        "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6"
+        "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71"
+        "60C980DD98EDD3DFFFFFFFFFFFFFFFFF",
+        "13"
+    },
+    {
+        /* null sentinel */
+        0,
+        0
+    }
+};
+
+static Ng_pair_t *new_ng( srp_Ng_type pair_type, const char *psz_N_hex, const char *psz_g_hex )
+{
+    Ng_pair_t *ng = malloc( sizeof( *ng ) );
+    const char *p_source = psz_N_hex == NULL ? Ng_hex_pairs[pair_type].psz_N : psz_N_hex;
+    const char *g_source = psz_g_hex == NULL ? Ng_hex_pairs[pair_type].psz_g : psz_g_hex;
+
+    gcry_error_t error = 0;
+    error |= gcry_mpi_scan( &ng->N, GCRYMPI_FMT_HEX, p_source, 0, NULL );
+    error |= gcry_mpi_scan( &ng->g, GCRYMPI_FMT_HEX, g_source, 0, NULL );
+    return error == 0 ? ng : NULL;
+}
+
+static void delete_ng( Ng_pair_t **pp_pair )
+{
+    if ( *pp_pair != NULL )
+    {
+        gcry_mpi_release( ( *pp_pair )->N );
+        gcry_mpi_release( ( *pp_pair )->g );
+        free( *pp_pair );
+    }
+    *pp_pair = NULL;
+}
+
+struct srp_verifier
+{
+    srp_hash_algorithm hash_alg;
+    Ng_pair_t *p_Ng_pair;
+    bool b_authenticated;
+
+    char *psz_username;
+    uint8_t *p_bytes_B;
+
+    uint8_t *p_bytes_M1;
+    uint8_t *p_bytes_M2;
+    uint8_t *p_bytes_K;
+};
+
+struct srp_user
+{
+    srp_hash_algorithm hash_alg;
+    Ng_pair_t *p_ng;
+
+    gcry_mpi_t a;
+    gcry_mpi_t A;
+    gcry_mpi_t S;
+
+    const uint8_t *p_bytes_A;
+    bool b_authenticated;
+
+    char *psz_username;
+    char *psz_password;
+    size_t i_password_len;
+    size_t i_username_len;
+
+    uint8_t *p_bytes_M1; // client verifier
+    uint8_t *p_bytes_M2; // server verifier
+
+    uint8_t *p_bytes_K; // shared key
+    size_t i_length_K;
+
+    void (*fp_calculate_K)( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *p_i_length_K );
+
+    void (*fp_calculate_M1)( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1,
+                             size_t *pi_length_M1 );
+};
+
+static int srp_hash_to_gcrypt( srp_hash_algorithm alg )
+{
+    switch ( alg )
+    {
+        case SRP_SHA1  :
+            return GCRY_MD_SHA1;
+        case SRP_SHA224:
+            return GCRY_MD_SHA224;
+        case SRP_SHA256:
+            return GCRY_MD_SHA256;
+        case SRP_SHA384:
+            return GCRY_MD_SHA384;
+        case SRP_SHA512:
+            return GCRY_MD_SHA512;
+        default:
+            return -1;
+    };
+}
+
+static size_t mpi_nbytes( gcry_mpi_t mpi )
+{
+    return ( gcry_mpi_get_nbits( mpi ) + 7 ) / 8;
+}
+
+// H(p_bytes_in)
+static void hash_bytes( srp_hash_algorithm alg, const uint8_t *p_bytes_in, size_t i_length, uint8_t **pp_bytes_out )
+{
+    size_t i_digest_size = srp_hash_length( alg );
+    *pp_bytes_out = calloc( i_digest_size, sizeof( **pp_bytes_out ) );
+    gcry_md_hash_buffer( srp_hash_to_gcrypt( alg ), *pp_bytes_out, p_bytes_in, i_length );
+}
+
+// H(mpi_to_bin(n))
+static void hash_mpi( srp_hash_algorithm alg, gcry_mpi_t n, uint8_t **pp_bytes_out, size_t *pi_length_out )
+{
+    size_t i_length = mpi_nbytes( n );
+    if ( pi_length_out != NULL ) *pi_length_out = i_length;
+
+    uint8_t *p_bytes = calloc( i_length, sizeof( *p_bytes ) );
+    gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes, i_length, NULL, n );
+
+    hash_bytes( alg, p_bytes, i_length, pp_bytes_out );
+    free( p_bytes );
+}
+
+// H(mpi_to_bin(n1) | mpi_to_bin(n2))
+static gcry_mpi_t *hash_mpi_concat_mpi( srp_hash_algorithm alg, gcry_mpi_t n1, gcry_mpi_t n2 )
+{
+    uint8_t *p_hashed_bytes = NULL;
+    size_t i_len_n1 = mpi_nbytes( n1 );
+    size_t i_len_n2 = mpi_nbytes( n2 );
+    uint8_t *n_n1_bytes = calloc( i_len_n1, sizeof( *n_n1_bytes ) );
+    uint8_t *n_n2_bytes = calloc( i_len_n2, sizeof( *n_n2_bytes ) );
+
+    gcry_mpi_print( GCRYMPI_FMT_USG, n_n1_bytes, i_len_n1, NULL, n1 );
+    gcry_mpi_print( GCRYMPI_FMT_USG, n_n2_bytes, i_len_n2, NULL, n2 );
+
+    gcry_md_hd_t digest_handle;
+    gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+    gcry_md_write( digest_handle, n_n1_bytes, i_len_n1 );
+    gcry_md_write( digest_handle, n_n2_bytes, i_len_n2 );
+    p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+    gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+    gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+    gcry_md_close( digest_handle );
+
+    free( n_n1_bytes );
+    free( n_n2_bytes );
+    return ret;
+}
+
+// H(PAD(n1) | PAD(n2))
+static gcry_mpi_t *hash_mpi_concat_mpi_padded( srp_hash_algorithm alg, size_t i_pad_length,
+                                               gcry_mpi_t n1, gcry_mpi_t n2 )
+{
+    uint8_t *p_hashed_bytes = NULL;
+    size_t i_len_n1 = mpi_nbytes( n1 );
+    size_t i_len_n2 = mpi_nbytes( n2 );
+
+    assert( i_len_n1 <= i_pad_length );
+    assert( i_len_n2 <= i_pad_length );
+
+    uint8_t *n_n1_bytes = calloc( i_pad_length, sizeof( *n_n1_bytes ) );
+    uint8_t *n_n2_bytes = calloc( i_pad_length, sizeof( *n_n2_bytes ) );
+
+    gcry_mpi_print( GCRYMPI_FMT_USG, n_n1_bytes + i_pad_length - i_len_n1, i_len_n1, NULL, n1 );
+    gcry_mpi_print( GCRYMPI_FMT_USG, n_n2_bytes + i_pad_length - i_len_n2, i_len_n2, NULL, n2 );
+
+    gcry_md_hd_t digest_handle;
+    gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+    gcry_md_write( digest_handle, n_n1_bytes, i_pad_length );
+    gcry_md_write( digest_handle, n_n2_bytes, i_pad_length );
+    p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+    gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+    gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+    gcry_md_close( digest_handle );
+
+    free( n_n1_bytes );
+    free( n_n2_bytes );
+    return ret;
+}
+
+// H(n | bytes)
+static gcry_mpi_t *hash_mpi_concat_bytes( srp_hash_algorithm alg, gcry_mpi_t n, const uint8_t *p_bytes,
+                                          size_t i_len_bytes )
+{
+    uint8_t *p_hashed_bytes = NULL;
+    size_t i_len_n = mpi_nbytes( n );
+    uint8_t *p_n_bytes = calloc( i_len_n, sizeof( *p_n_bytes ) );
+
+    gcry_mpi_print( GCRYMPI_FMT_USG, p_n_bytes, i_len_n, NULL, n );
+
+    gcry_md_hd_t digest_handle;
+    gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+    gcry_md_write( digest_handle, p_n_bytes, i_len_n );
+    gcry_md_write( digest_handle, p_bytes, i_len_bytes );
+    p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+    gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+    gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+    gcry_md_close( digest_handle );
+
+    free( p_n_bytes );
+    return ret;
+}
+
+// H(s | H(I | ":" | P))
+static gcry_mpi_t *calculate_x( srp_hash_algorithm alg, gcry_mpi_t salt,
+                                const char *psz_username, size_t i_username_len,
+                                const char *psz_password, size_t i_password_len )
+{
+    gcry_md_hd_t digest_handle;
+
+    gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+    gcry_md_write( digest_handle, psz_username, i_username_len );
+    gcry_md_write( digest_handle, ":", 1 );
+    gcry_md_write( digest_handle, psz_password, i_password_len );
+
+    gcry_mpi_t *ret = hash_mpi_concat_bytes(
+        alg, salt,
+        gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) ),
+        gcry_md_get_algo_dlen( gcry_md_get_algo( digest_handle ) )
+    );
+
+    gcry_md_close( digest_handle );
+    return ret;
+}
+
+// updates the digest handle with the mpi in p_n
+static void md_write_mpi( gcry_md_hd_t p_digest_handle, gcry_mpi_t n )
+{
+    size_t len = mpi_nbytes( n );
+    uint8_t *p_bytes = calloc( len, sizeof( *p_bytes ) );
+
+    gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes, len, NULL, n );
+    gcry_md_write( p_digest_handle, p_bytes, len );
+    free( p_bytes );
+}
+
+static void srp_calculate_K( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *pi_length_K )
+{
+    return hash_mpi( alg, S, pp_bytes_K, pi_length_K );
+}
+
+// H(S ~ 00 00 00 00) ~ H(S ~ 00 00 00 01)
+static void apple_calculate_K( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *pi_length_K )
+{
+    uint8_t first_bytes[4] = {0, 0, 0, 0};
+    uint8_t second_bytes[4] = {0, 0, 0, 1};
+    size_t i_hash_length = srp_hash_length( alg );
+    *pp_bytes_K = malloc( i_hash_length * 2 * sizeof( **pp_bytes_K ) );
+    if ( pi_length_K != NULL ) *pi_length_K = i_hash_length * 2;
+
+    gcry_mpi_t *p_K1 = hash_mpi_concat_bytes( alg, S, first_bytes, 4 );
+    gcry_mpi_t *p_K2 = hash_mpi_concat_bytes( alg, S, second_bytes, 4 );
+
+    uint8_t p_K1_bytes[i_hash_length];
+    uint8_t p_K2_bytes[i_hash_length];
+    gcry_mpi_print( GCRYMPI_FMT_USG, p_K1_bytes, i_hash_length, NULL, *p_K1 );
+    gcry_mpi_print( GCRYMPI_FMT_USG, p_K2_bytes, i_hash_length, NULL, *p_K2 );
+
+    memcpy( *pp_bytes_K, p_K1_bytes, i_hash_length );
+    memcpy( *pp_bytes_K + i_hash_length, p_K2_bytes, i_hash_length );
+
+    gcry_mpi_release( *p_K1 );
+    gcry_mpi_release( *p_K2 );
+}
+
+// S = ((B - k * (g^x % N)) ^ (a + u * x)) % N
+static void calculate_S( const struct srp_user *p_user, const gcry_mpi_t *B, const gcry_mpi_t *u, const gcry_mpi_t *x,
+                         const gcry_mpi_t *k, gcry_mpi_t *S )
+{
+    gcry_mpi_t tmp1 = gcry_mpi_new( 128 );
+    gcry_mpi_t tmp2 = gcry_mpi_new( 128 );
+    gcry_mpi_t tmp3 = gcry_mpi_new( 128 );
+
+    gcry_mpi_mul( tmp1, *u, *x ); // u * x
+    gcry_mpi_add( tmp2, p_user->a, tmp1 ); // a + u * x
+    gcry_mpi_powm( tmp1, p_user->p_ng->g, *x, p_user->p_ng->N ); // g^x % N
+    gcry_mpi_mul( tmp3, *k, tmp1 ); // k * (g^x % N)
+    gcry_mpi_sub( tmp1, *B, tmp3 ); // B - k * (g^x % N)
+    gcry_mpi_powm( *S, tmp1, tmp2, p_user->p_ng->N ); // ((B - k * (g^x % N)) ^ (a + u * x)) % N
+
+    gcry_mpi_release( tmp1 );
+    gcry_mpi_release( tmp2 );
+    gcry_mpi_release( tmp3 );
+}
+
+// p_bytes_M1 = H(A | B | S)
+static void
+srp_calculate_M1( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+    (void)( s );
+    gcry_md_hd_t digest_handle;
+    int gcrypt_alg = srp_hash_to_gcrypt( p_user->hash_alg );
+
+    gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+    md_write_mpi( digest_handle, p_user->A );
+    md_write_mpi( digest_handle, B );
+    md_write_mpi( digest_handle, p_user->S );
+    const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+    if ( pi_length_M1 != NULL )
+    {
+        *pi_length_M1 = srp_hash_length( p_user->hash_alg );
+    }
+    *pp_bytes_M1 = malloc( *pi_length_M1 * sizeof( **pp_bytes_M1 ) );
+    memcpy( *pp_bytes_M1, p_hashed, *pi_length_M1 );
+    gcry_md_close( digest_handle );
+}
+
+// p_bytes_M1 = H(H(N) xor H(g) ~ H(I) ~ s ~ A ~ B ~ K)
+static void
+apple_calculate_M1( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+    size_t i_digest_length = srp_hash_length( p_user->hash_alg );
+    gcry_md_hd_t digest_handle;
+    uint8_t *H_N = NULL;
+    uint8_t *H_g = NULL;
+    uint8_t *H_I = NULL;
+    uint8_t *HN_xor_Hg = calloc( i_digest_length, sizeof( *HN_xor_Hg ) );
+
+    hash_mpi( p_user->hash_alg, p_user->p_ng->N, &H_N, NULL );
+    hash_mpi( p_user->hash_alg, p_user->p_ng->g, &H_g, NULL );
+    hash_bytes( p_user->hash_alg, (const uint8_t *)p_user->psz_username, strlen( p_user->psz_username ), &H_I );
+
+    for ( size_t i = 0; i < i_digest_length; i++ )
+        HN_xor_Hg[i] = H_N[i] ^ H_g[i];
+
+    int gcrypt_alg = srp_hash_to_gcrypt( p_user->hash_alg );
+
+    uint8_t *p_bytes_K;
+    srp_user_get_hashed_session_key( p_user, &p_bytes_K, NULL );
+
+    gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+    gcry_md_write( digest_handle, HN_xor_Hg, i_digest_length );
+    gcry_md_write( digest_handle, H_I, i_digest_length );
+    md_write_mpi( digest_handle, s );
+    md_write_mpi( digest_handle, p_user->A );
+    md_write_mpi( digest_handle, B );
+    gcry_md_write( digest_handle, p_bytes_K, i_digest_length * 2 );
+    const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+    if ( pi_length_M1 != NULL )
+    {
+        *pi_length_M1 = i_digest_length;
+    }
+    *pp_bytes_M1 = malloc( i_digest_length * sizeof( **pp_bytes_M1 ) );
+    memcpy( *pp_bytes_M1, p_hashed, i_digest_length );
+    gcry_md_close( digest_handle );
+
+    free( p_bytes_K );
+}
+
+// p_bytes_M2 = H(A ~ p_bytes_M1 ~ K)
+static void
+calculate_M2( srp_hash_algorithm alg, gcry_mpi_t A, const uint8_t *p_bytes_M1, const uint8_t *p_bytes_K,
+              uint8_t **pp_bytes_M2, size_t *pi_length_M2 )
+{
+    gcry_md_hd_t digest_handle;
+    size_t i_digest_length = srp_hash_length( alg );
+    int gcrypt_alg = srp_hash_to_gcrypt( alg );
+
+    gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+    md_write_mpi( digest_handle, A );
+    gcry_md_write( digest_handle, p_bytes_M1, i_digest_length );
+    gcry_md_write( digest_handle, p_bytes_K, i_digest_length * 2 );
+    const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+    if ( pi_length_M2 != NULL )
+    {
+        *pi_length_M2 = i_digest_length;
+    }
+    *pp_bytes_M2 = malloc( i_digest_length * sizeof( **pp_bytes_M2 ) );
+    memcpy( *pp_bytes_M2, p_hashed, i_digest_length );
+    gcry_md_close( digest_handle );
+}
+
+/***********************************************************************************************************
+ *
+ *  Exported Functions
+ *
+ ***********************************************************************************************************/
+
+size_t srp_hash_length( srp_hash_algorithm alg )
+{
+    return gcry_md_get_algo_dlen( srp_hash_to_gcrypt( alg ) );
+}
+
+// Verifier code will be used for airplay receiving.
+// It currently is not being used and may be wrong.
+//
+//void srp_create_salted_verification_key( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+//                                         const char *psz_username, size_t i_length_username,
+//                                         const char *psz_password, size_t i_length_password,
+//                                         uint8_t **pp_bytes_s, size_t *pi_length_s,
+//                                         uint8_t **pp_bytes_v, size_t *pi_length_v,
+//                                         const char *psz_N_hex, const char *psz_g_hex )
+//{
+//    gcry_mpi_t s = gcry_mpi_new( 256 );
+//    gcry_mpi_t v = gcry_mpi_new( 256 );
+//    gcry_mpi_t *x = NULL;
+//    Ng_pair_t *ng = new_ng( Ng_type, psz_N_hex, psz_g_hex );
+//
+//    if ( !s || !v || !ng )
+//        goto cleanup_and_exit;
+//
+//    gcry_mpi_randomize( s, 32, GCRY_STRONG_RANDOM );
+//
+//    x = calculate_x( alg, s, psz_username, i_length_username, psz_password, i_length_password );
+//
+//    gcry_mpi_powm( v, ng->g, *x, ng->N );
+//
+//    *pi_length_s = mpi_nbytes( s );
+//    *pi_length_v = mpi_nbytes( v );
+//    *pp_bytes_s = malloc( *pi_length_s * sizeof( **pp_bytes_s ) );
+//    *pp_bytes_v = malloc( *pi_length_v * sizeof( **pp_bytes_v ) );
+//
+//    gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_s, *pi_length_s, NULL, s );
+//    gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_v, *pi_length_v, NULL, v );
+//
+//    cleanup_and_exit:
+//    delete_ng( &ng );
+//    gcry_mpi_release( s );
+//    gcry_mpi_release( v );
+//    gcry_mpi_release( *x );
+//}
+//
+//
+///* Out: p_bytes_B, len_B.
+// *
+// * On failure, p_bytes_B will be set to NULL and len_B will be set to 0
+// */
+//struct srp_verifier *srp_verifier_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+//                                       const char *psz_username, size_t i_length_username,
+//                                       const uint8_t *p_bytes_s, size_t i_length_s,
+//                                       const uint8_t *p_bytes_v, size_t i_length_v,
+//                                       const uint8_t *p_bytes_A, size_t i_length_A,
+//                                       uint8_t **pp_bytes_B, size_t *pi_length_B,
+//                                       const char *psz_N_hex, const char *psz_g_hex )
+//{
+//    gcry_mpi_t s;
+//    gcry_mpi_t v;
+//    gcry_mpi_t A;
+//    gcry_mpi_t *u = NULL;
+//    gcry_mpi_t *k = NULL;
+//    gcry_mpi_t B = gcry_mpi_new( 128 );
+//    gcry_mpi_t S = gcry_mpi_new( 128 );
+//    gcry_mpi_t b = gcry_mpi_new( 128 );
+//    gcry_mpi_t tmp1 = gcry_mpi_new( 128 );
+//    gcry_mpi_t tmp2 = gcry_mpi_new( 128 );
+//    Ng_pair_t *ng = new_ng( Ng_type, psz_N_hex, psz_g_hex );
+//    struct srp_verifier *ver = NULL;
+//
+//    gcry_mpi_scan( &s, GCRYMPI_FMT_USG, p_bytes_s, (size_t)i_length_s, NULL );
+//    gcry_mpi_scan( &v, GCRYMPI_FMT_USG, p_bytes_v, (size_t)i_length_v, NULL );
+//    gcry_mpi_scan( &A, GCRYMPI_FMT_USG, p_bytes_A, (size_t)i_length_A, NULL );
+//
+//    *pi_length_B = 0;
+//    *pp_bytes_B = 0;
+//
+//    if ( !s || !v || !A || !B || !S || !b || !tmp1 || !tmp2 || !ng )
+//        goto cleanup_and_exit;
+//
+//    ver = calloc( 1, sizeof( ver ) );
+//
+//    if ( !ver )
+//        goto cleanup_and_exit;
+//
+//    ver->hash_alg = alg;
+//    ver->p_Ng_pair = ng;
+//    ver->b_authenticated = false;
+//
+//    ver->psz_username = malloc( ( i_length_username + 1 ) * sizeof( ver->psz_username ) );
+//    memcpy( ver->psz_username, psz_username, ( i_length_username + 1 ) );
+//
+//    /* SRP-6a safety check */
+//    gcry_mpi_mod( tmp1, A, ng->N );
+//    if ( gcry_mpi_cmp_ui( tmp1, 0 ) )
+//    {
+//        gcry_mpi_randomize( b, 256, GCRY_STRONG_RANDOM );
+//        k = hash_mpi_concat_mpi( alg, ng->N, ng->g );
+//
+//        /* B = (k * v + (g^b % N)) % N */
+//        gcry_mpi_mul( tmp1, *k, v );
+//        gcry_mpi_powm( tmp2, ng->g, b, ng->N );
+//        gcry_mpi_addm( B, tmp1, tmp2, ng->N );
+//        u = hash_mpi_concat_mpi( alg, A, B );
+//
+//        /* S = (A *(v^u)) ^ b */
+//        gcry_mpi_powm( tmp1, v, *u, ng->N );
+//        gcry_mpi_mul( tmp2, A, tmp1 );
+//        gcry_mpi_powm( S, tmp2, b, ng->N );
+//
+//        hash_mpi( alg, S, &ver->p_bytes_K, NULL );
+//
+//        // calculate p_bytes_M1 and p_bytes_M2
+//        apple_calculate_M1( NULL, s, B, &ver->p_bytes_M1, NULL );
+//        calculate_M2( alg, A, ver->p_bytes_M1, ver->p_bytes_K, &ver->p_bytes_M2, NULL );
+//
+//        *pi_length_B = mpi_nbytes( B );
+//        *pp_bytes_B = calloc( (size_t)*pi_length_B, sizeof( *pp_bytes_B ) );
+//
+//        gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_B, *pi_length_B, NULL, B );
+//
+//        ver->p_bytes_B = *pp_bytes_B;
+//    }
+//
+//    cleanup_and_exit:
+//    gcry_mpi_release( s );
+//    gcry_mpi_release( v );
+//    gcry_mpi_release( A );
+//    if ( u != NULL ) gcry_mpi_release( *u );
+//    if ( k != NULL ) gcry_mpi_release( *k );
+//    gcry_mpi_release( B );
+//    gcry_mpi_release( S );
+//    gcry_mpi_release( b );
+//    gcry_mpi_release( tmp1 );
+//    gcry_mpi_release( tmp2 );
+//    return ver;
+//}
+//
+//void srp_verifier_delete( struct srp_verifier **p_verifier )
+//{
+//    if ( p_verifier != NULL )
+//    {
+//        delete_ng( &( *p_verifier )->p_Ng_pair );
+//        free( ( *p_verifier )->psz_username );
+//        free( ( *p_verifier )->p_bytes_B );
+//        free( *p_verifier );
+//        *p_verifier = NULL;
+//    }
+//}
+//
+//
+//bool srp_verifier_is_authenticated( struct srp_verifier *p_verifier )
+//{
+//    return p_verifier->b_authenticated;
+//}
+//
+//
+//const char *srp_verifier_get_username( struct srp_verifier *p_verifier )
+//{
+//    return p_verifier->psz_username;
+//}
+//
+//void srp_verifier_verify_session( struct srp_verifier *p_verifier, const uint8_t *p_bytes_M1, size_t i_length_M1,
+//                                  const uint8_t **pp_bytes_M2, size_t *pi_length_M2 )
+//{
+//    assert( i_length_M1 == srp_verifier_get_session_key_length( p_verifier ) );
+//    if ( memcmp( p_verifier->p_bytes_M1, p_bytes_M1, srp_verifier_get_session_key_length( p_verifier ) ) == 0 )
+//    {
+//        p_verifier->b_authenticated = true;
+//        *pp_bytes_M2 = p_verifier->p_bytes_M2;
+//        *pi_length_M2 = i_length_M1;
+//    } else
+//    {
+//        *pp_bytes_M2 = NULL;
+//    }
+//}
+//
+//const uint8_t *srp_verifier_get_session_key( struct srp_verifier *p_verifier, size_t *key_length )
+//{
+//    if ( key_length )
+//        *key_length = srp_verifier_get_session_key_length( p_verifier );
+//    return p_verifier->p_bytes_K;
+//}
+//
+//size_t srp_verifier_get_session_key_length( struct srp_verifier *p_verifier )
+//{
+//    return srp_hash_length( p_verifier->hash_alg );
+//}
+
+/*******************************************************************************/
+
+// initializes the user
+struct srp_user *_srp_user_new( srp_hash_algorithm alg, srp_Ng_type ng_type,
+                                const char *psz_username, size_t i_len_username,
+                                const char *psz_password, size_t i_len_password,
+                                const char *psz_n_hex, const char *psz_g_hex,
+                                void (*fp_calculate_K)( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K,
+                                                        size_t *pi_length_K ),
+                                void (*fp_calculate_M1)( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B,
+                                                         uint8_t **pp_bytes_M1, size_t *pi_length_M1 ) )
+{
+    assert( fp_calculate_K != NULL );
+    assert( fp_calculate_M1 != NULL );
+
+    struct srp_user *usr = calloc( 1, sizeof( *usr ) );
+
+    if ( !usr )
+        goto err_exit;
+
+    usr->hash_alg = alg;
+    usr->p_ng = new_ng( ng_type, psz_n_hex, psz_g_hex );
+
+    usr->a = gcry_mpi_new( 256 );
+    usr->A = gcry_mpi_new( 128 );
+    usr->S = gcry_mpi_new( 128 );
+
+    if ( !usr->p_ng || !usr->a || !usr->A || !usr->S )
+        goto err_exit;
+
+    usr->psz_username = calloc( i_len_username + 1, sizeof( usr->psz_username ) );
+    usr->psz_password = calloc( i_len_password + 1, sizeof( usr->psz_password ) );
+    usr->i_password_len = i_len_password;
+    usr->i_username_len = i_len_username;
+
+    if ( !usr->psz_username || !usr->psz_password )
+        goto err_exit;
+
+    strncpy( usr->psz_username, psz_username, i_len_username + 1 );
+    strncpy( usr->psz_password, psz_password, i_len_password + 1 );
+
+    usr->b_authenticated = false;
+
+    usr->p_bytes_A = NULL;
+
+    usr->fp_calculate_K = fp_calculate_K;
+    usr->fp_calculate_M1 = fp_calculate_M1;
+
+    return usr;
+
+    err_exit:
+    if ( usr )
+    {
+        gcry_mpi_release( usr->a );
+        gcry_mpi_release( usr->A );
+        gcry_mpi_release( usr->S );
+        if ( usr->psz_username )
+            free( (void *)usr->psz_username );
+        if ( usr->psz_password )
+        {
+            memset( (void *)usr->psz_password, 0, usr->i_password_len );
+            free( (void *)usr->psz_password );
+        }
+        free( usr );
+    }
+
+    return NULL;
+}
+
+struct srp_user *srp_user_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+                               const char *psz_username, size_t i_length_username,
+                               const char *psz_password, size_t i_length_password,
+                               const char *psz_N_hex, const char *psz_g_hex )
+{
+    return _srp_user_new( alg, Ng_type, psz_username, i_length_username, psz_password, i_length_password, psz_N_hex,
+                          psz_g_hex, srp_calculate_K, srp_calculate_M1 );
+}
+
+struct srp_user *apple_srp_user_new( const char *psz_username, size_t i_len_username,
+                                     const char *psz_password, size_t i_len_password )
+{
+    return _srp_user_new( SRP_SHA1, SRP_NG_2048, psz_username, i_len_username, psz_password, i_len_password, NULL, NULL,
+                          apple_calculate_K, apple_calculate_M1 );
+}
+
+
+void srp_user_delete( struct srp_user *usr )
+{
+    if ( usr )
+    {
+        gcry_mpi_release( usr->a );
+        gcry_mpi_release( usr->A );
+        gcry_mpi_release( usr->S );
+
+        delete_ng( &usr->p_ng );
+
+        memset( (void *)usr->psz_password, 0, usr->i_password_len );
+
+        free( (char *)usr->psz_username );
+        free( (char *)usr->psz_password );
+
+        if ( usr->p_bytes_A )
+            free( (char *)usr->p_bytes_A );
+
+        memset( usr, 0, sizeof( *usr ) );
+        free( usr );
+    }
+}
+
+
+bool srp_user_is_authenticated( struct srp_user *p_user )
+{
+    return p_user->b_authenticated;
+}
+
+
+const char *srp_user_get_username( struct srp_user *usr )
+{
+    return usr->psz_username;
+}
+
+size_t srp_user_get_session_key_length( struct srp_user *p_user )
+{
+    return srp_hash_length( p_user->hash_alg );
+}
+
+void srp_user_get_hashed_session_key( const struct srp_user *p_user, uint8_t **pp_bytes_key, size_t *pi_length_k )
+{
+    size_t i_length_K = p_user->i_length_K;
+    uint8_t *p_bytes_K = malloc( i_length_K * sizeof( *p_bytes_K ) );
+    memcpy( p_bytes_K, p_user->p_bytes_K, i_length_K * sizeof( *p_bytes_K ) );
+
+    if ( pi_length_k )
+        *pi_length_k = i_length_K;
+
+    if ( pp_bytes_key )
+        *pp_bytes_key = p_bytes_K;
+}
+
+// generates a private key, and the corresponding public key A = g^a % N
+void srp_user_start_authentication( struct srp_user *p_user, const gcry_mpi_t *p_a, uint8_t **pp_bytes_A,
+                                    size_t *p_length_A )
+{
+    // A = g ^ a % N
+    if ( p_a != NULL )
+    {
+        p_user->a = *p_a;
+    } else
+    {
+        gcry_mpi_randomize( p_user->a, 256, GCRY_STRONG_RANDOM );
+    }
+
+    gcry_mpi_powm( p_user->A, p_user->p_ng->g, p_user->a, p_user->p_ng->N );
+
+    *p_length_A = mpi_nbytes( p_user->A );
+    *pp_bytes_A = malloc( *p_length_A * sizeof( **pp_bytes_A ) );
+    gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_A, *p_length_A, NULL, p_user->A );
+
+    p_user->p_bytes_A = *pp_bytes_A;
+}
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+void srp_user_process_challenge( struct srp_user *p_user,
+                                 const uint8_t *p_bytes_s, size_t i_length_s,
+                                 const uint8_t *p_bytes_B, size_t i_length_B,
+                                 uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+    gcry_mpi_t s;
+    gcry_mpi_t B;
+    gcry_mpi_t *u = NULL;
+    gcry_mpi_t *x = NULL;
+    gcry_mpi_t *k = NULL;
+
+    gcry_mpi_scan( &s, GCRYMPI_FMT_USG, p_bytes_s, i_length_s, NULL );
+    gcry_mpi_scan( &B, GCRYMPI_FMT_USG, p_bytes_B, i_length_B, NULL );
+
+    if ( !s || !B )
+        goto cleanup_and_exit;
+
+    u = hash_mpi_concat_mpi( p_user->hash_alg, p_user->A, B );
+
+    x = calculate_x( p_user->hash_alg, s, p_user->psz_username, p_user->i_username_len, p_user->psz_password,
+                     p_user->i_password_len );
+
+    k = hash_mpi_concat_mpi_padded( p_user->hash_alg, mpi_nbytes( p_user->p_ng->N ), p_user->p_ng->N,
+                                    p_user->p_ng->g );
+
+    /* SRP-6a safety check */
+    if ( gcry_mpi_cmp_ui( B, 0 ) == 1 && gcry_mpi_cmp_ui( *u, 0 ) == 1 )
+    {
+
+        calculate_S( p_user, &B, u, x, k, &p_user->S );
+
+        p_user->fp_calculate_K( p_user->hash_alg, p_user->S, &p_user->p_bytes_K, &p_user->i_length_K );
+        p_user->fp_calculate_M1( p_user, s, B, &p_user->p_bytes_M1, NULL );
+
+        calculate_M2( p_user->hash_alg, p_user->A, p_user->p_bytes_M1, p_user->p_bytes_K, &p_user->p_bytes_M2, NULL );
+
+        *pp_bytes_M1 = p_user->p_bytes_M1;
+        *pi_length_M1 = srp_hash_length( p_user->hash_alg );
+    }
+
+    cleanup_and_exit:
+    gcry_mpi_release( s );
+    gcry_mpi_release( B );
+    if ( u != NULL ) gcry_mpi_release( *u );
+    if ( x != NULL ) gcry_mpi_release( *x );
+    if ( k != NULL ) gcry_mpi_release( *k );
+}
+
+bool srp_user_verify_session( struct srp_user *p_user, const uint8_t *p_bytes_M2 )
+{
+    if ( memcmp( p_user->p_bytes_M2, p_bytes_M2, srp_hash_length( p_user->hash_alg ) ) == 0 )
+        p_user->b_authenticated = 1;
+
+    return srp_user_is_authenticated( p_user );
+}
diff --git a/modules/stream_out/airplay/csrp/srp.h b/modules/stream_out/airplay/csrp/srp.h
new file mode 100755
index 0000000000..aaf90a32e9
--- /dev/null
+++ b/modules/stream_out/airplay/csrp/srp.h
@@ -0,0 +1,233 @@
+/*
+ * A Secure Remote Password 6a implementation (RFC 5054)
+ * Also includes Apple-Flavoured SRP which changes a
+ * few of the functions to be compatible with Apple TVs.
+ *
+ * Adapted from CSRP by Tom Cocagne by Alexander Lyon.
+ * https://github.com/cocagne/csrp
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Tom Cocagne, Alexander Lyon
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Purpose:         This is an implementation of Secure Remote Password protocol version 6a 
+ *                  as described in RFC5054 as well as "Apple Flavoured" SRP which is detailed
+ *                  here: https://code.videolan.org/GSoC2018/arlyon/vlc/wikis/AirPlay%201/SRP6
+ * 
+ * Original Author: tom.cocagne at gmail.com (Tom Cocagne)
+ * Adapted By:      arlyon at me.com (Alexander Lyon)
+ * 
+ * Dependencies:    libgcrypt
+ * 
+ * Usage:           Refer to test_srp.c for a demonstration
+ * 
+ * Notes:
+ *    This library allows multiple combinations of hashing algorithms and 
+ *    prime number constants. For authentication to succeed, the hash and
+ *    prime number constants must match between 
+ *    srp_create_salted_verification_key(), srp_user_new(),
+ *    and srp_verifier_new(). A recommended approach is to determine the
+ *    desired level of security for an application and globally define the
+ *    hash and prime number constants to the predetermined values.
+ * 
+ *    As one might suspect, more bits means more security. As one might also
+ *    suspect, more bits also means more processing time. The test_srp.c 
+ *    program can be easily modified to profile various combinations of 
+ *    hash & prime number pairings.
+ */
+
+#ifndef SRP_H
+#define SRP_H
+
+struct srp_verifier;
+struct srp_user;
+
+typedef enum
+{
+    SRP_NG_1024,
+    SRP_NG_2048,
+    SRP_NG_4096,
+    SRP_NG_8192,
+    SRP_NG_CUSTOM
+} srp_Ng_type;
+
+typedef enum
+{
+    SRP_SHA1,
+    SRP_SHA224,
+    SRP_SHA256,
+    SRP_SHA384,
+    SRP_SHA512
+} srp_hash_algorithm;
+
+/*****************************************************************************
+ * Util
+ *****************************************************************************/
+
+/**
+ * Gets the hash length for a given SRP hash algorithm.
+ *
+ * @param alg The requested algorithm.
+ * @return The hash length.
+ */
+size_t srp_hash_length( srp_hash_algorithm alg );
+
+/**
+ * Creates the salted verification key for the given username and password.
+ *
+ * @param[out] pp_bytes_s A pointer to the client salt.
+ * @param[out] pp_bytes_v A pointer to the password verifier.
+ * @param psz_N_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param psz_g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of pp_bytes_s, len_s, bytes_v and len_v is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ *        If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+void srp_create_salted_verification_key( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+                                         const char *psz_username, size_t i_length_username,
+                                         const char *psz_password, size_t i_length_password,
+                                         uint8_t **pp_bytes_s, size_t *pi_length_s,
+                                         uint8_t **pp_bytes_v, size_t *pi_length_v,
+                                         const char *psz_N_hex, const char *psz_g_hex );
+
+/*****************************************************************************
+ * Verifier
+ *****************************************************************************/
+
+/**
+ * Creates a new SRPVerifier and generates the second public ephemeral value.
+ *
+ * @param[out] pp_bytes_B A pointer to the server ephemeral public key.
+ * @param psz_N_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param psz_g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of bytes_B and the return value is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ *        If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+struct srp_verifier *srp_verifier_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+                                       const char *psz_username, size_t i_length_username,
+                                       const uint8_t *p_bytes_s, size_t i_length_s,
+                                       const uint8_t *p_bytes_v, size_t i_length_v,
+                                       const uint8_t *p_bytes_A, size_t i_length_A,
+                                       uint8_t **pp_bytes_B, size_t *pi_length_B,
+                                       const char *psz_N_hex, const char *psz_g_hex );
+
+/**
+ * Checks if the verifier session key is equal to the user session key.
+ *
+ * @param[out] pp_bytes_M2 A pointer to the M2 value.
+ */
+void srp_verifier_verify_session( struct srp_verifier *p_verifier, const uint8_t *p_bytes_M1, size_t i_length_M1,
+                                  const uint8_t **pp_bytes_M2, size_t *pi_length_M2 );
+
+void srp_verifier_delete( struct srp_verifier **p_verifier );
+
+bool srp_verifier_is_authenticated( struct srp_verifier *p_verifier );
+
+const char *srp_verifier_get_username( struct srp_verifier *p_verifier );
+
+/**
+ * @param[out] key_length The length of the session key. Can be NULL, in which case it is ignored.
+ * @return The session key.
+ */
+const uint8_t *srp_verifier_get_session_key( struct srp_verifier *p_verifier, size_t *key_length );
+
+size_t srp_verifier_get_session_key_length( struct srp_verifier *p_verifier );
+
+/*****************************************************************************
+ * User
+ *****************************************************************************/
+
+/**
+ * Creates a new srp user.
+ *
+ * @param n_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of the return value is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ *        If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+struct srp_user *srp_user_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+                               const char *psz_username, size_t i_length_username,
+                               const char *psz_password, size_t i_length_password,
+                               const char *psz_N_hex, const char *psz_g_hex );
+
+/**
+ * Creates an instance of an apple flavoured SRP user.
+ *
+ * @see srp_user_new
+ */
+struct srp_user *apple_srp_user_new( const char *psz_username, size_t i_len_username,
+                                     const char *psz_password, size_t i_len_password );
+
+/**
+ * Generates the first ephemeral public key and username from the supplied SRPUser.
+ *
+ * @param p_a If non-null, a is used as the private key.
+ * @param[out] pp_bytes_A A pointer to the client ephemeral public key.
+ *
+ * @note: Freeing of bytes_A is the responsibility of the user.
+ */
+void srp_user_start_authentication( struct srp_user *p_user, const gcry_mpi_t *p_a,
+                                    uint8_t **pp_bytes_A, size_t *p_length_A );
+
+/**
+ * Calculates M1 from s and B.
+ *
+ * @param[out] pp_bytes_M1 The session key verifier.
+ *
+ * @note: Freeing of bytes_M1 is the responsibility of the user.
+ */
+void srp_user_process_challenge( struct srp_user *p_user,
+                                 const uint8_t *p_bytes_s, size_t i_length_s,
+                                 const uint8_t *p_bytes_B, size_t i_length_B,
+                                 uint8_t **pp_bytes_M1, size_t *pi_length_M1 );
+
+/**
+ * Authenticates the given SRPUser if the supplied M2 (from the verifier) is the same as the one of the user.
+ * @param p_user The user to verify.
+ * @param p_bytes_M2 The M2 from the server.
+ * @return true if the user is authenticated as a result otherwise false.
+ *
+ * @note p_bytes_M2 must be exactly srp_user_get_session_key_length() bytes in size
+ */
+bool srp_user_verify_session( struct srp_user *p_user, const uint8_t *p_bytes_M2 );
+
+void srp_user_delete( struct srp_user *usr );
+
+bool srp_user_is_authenticated( struct srp_user *p_user );
+
+const char *srp_user_get_username( struct srp_user *usr );
+
+/**
+ * @param[out] pi_length_k The length of the session key. Can be NULL, in which case it is ignored.
+ * @return The session key. This memory must be free'd.
+ */
+void srp_user_get_hashed_session_key( const struct srp_user *p_user, uint8_t **pp_bytes_key, size_t *pi_length_k );
+
+size_t srp_user_get_session_key_length( struct srp_user *p_user );
+
+#endif
-- 
2.19.0



More information about the vlc-devel mailing list