@@ -301,6 +301,8 @@ External library support:
--enable-mbedtls enable mbedTLS, needed for https support
if openssl, gnutls or libtls is not used [no]
--enable-mediacodec enable Android MediaCodec support [no]
+ --disable-nwf disable Apple Network.framework, needed for TLS support
+ on macOS if openssl and gnutls are not used [autodetect]
--enable-libmysofa enable libmysofa, needed for sofalizer filter [no]
--enable-openal enable OpenAL 1.1 capture support [no]
--enable-opencl enable OpenCL processing [no]
@@ -1693,6 +1695,7 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST="
libxcb_shape
libxcb_xfixes
lzma
+ nwf
schannel
sdl2
securetransport
@@ -3357,6 +3360,7 @@ https_protocol_suggest="zlib"
icecast_protocol_select="http_protocol"
mmsh_protocol_select="http_protocol"
mmst_protocol_select="network"
+nwf_conflict="openssl gnutls libtls mbedtls"
rtmp_protocol_conflict="librtmp_protocol"
rtmp_protocol_select="tcp_protocol"
rtmp_protocol_suggest="zlib"
@@ -3378,7 +3382,7 @@ sctp_protocol_select="network"
securetransport_conflict="openssl gnutls libtls mbedtls"
srtp_protocol_select="rtp_protocol srtp"
tcp_protocol_select="network"
-tls_protocol_deps_any="gnutls openssl schannel securetransport libtls mbedtls"
+tls_protocol_deps_any="gnutls nwf openssl schannel securetransport libtls mbedtls"
tls_protocol_select="tcp_protocol"
udp_protocol_select="network"
udplite_protocol_select="network"
@@ -6353,8 +6357,11 @@ if enabled decklink; then
esac
fi
+enabled nwf &&
+ check_lib nwf "Network/Network.h" "nw_connection_create" "-Wl,-framework,CoreFoundation -Wl,-framework,Security -Wl,-weak_framework,Network" ||
+ disable nwf
+
enabled securetransport &&
- check_func SecIdentityCreate "-Wl,-framework,CoreFoundation -Wl,-framework,Security" &&
check_lib securetransport "Security/SecureTransport.h Security/Security.h" "SSLCreateContext" "-Wl,-framework,CoreFoundation -Wl,-framework,Security" ||
disable securetransport
@@ -613,6 +613,7 @@ OBJS-$(CONFIG_TCP_PROTOCOL) += tcp.o
TLS-OBJS-$(CONFIG_GNUTLS) += tls_gnutls.o
TLS-OBJS-$(CONFIG_LIBTLS) += tls_libtls.o
TLS-OBJS-$(CONFIG_MBEDTLS) += tls_mbedtls.o
+TLS-OBJS-$(CONFIG_NWF) += tls_apple.o
TLS-OBJS-$(CONFIG_OPENSSL) += tls_openssl.o
TLS-OBJS-$(CONFIG_SECURETRANSPORT) += tls_apple.o
TLS-OBJS-$(CONFIG_SCHANNEL) += tls_schannel.o
@@ -19,7 +19,7 @@
*/
#include <errno.h>
-
+#include <pthread.h>
#include "avformat.h"
#include "avio_internal.h"
@@ -37,18 +37,88 @@
#include <Security/SecureTransport.h>
#include <CoreFoundation/CoreFoundation.h>
+#if CONFIG_NWF
+#include <Network/Network.h>
+#endif
+
// We use a private API call here; it's good enough for WebKit.
SecIdentityRef __attribute__((weak)) SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);
#define ioErr -36
+#define NWF_CHECK __builtin_available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
+
typedef struct TLSContext {
const AVClass *class;
TLSShared tls_shared;
SSLContextRef ssl_context;
CFArrayRef ca_array;
int lastErr;
+#if CONFIG_NWF
+ nw_connection_t nw_conn;
+ dispatch_semaphore_t semaphore;
+ dispatch_semaphore_t state_semaphore;
+ pthread_mutex_t state_lock;
+ int at_eof;
+ nw_listener_state_t nw_listen_state;
+ nw_connection_state_t nw_state;
+ nw_error_t nw_state_error;
+ int tcp_nodelay;
+#endif
} TLSContext;
+#if CONFIG_NWF
+static int nwf_init = 0;
+static dispatch_queue_t nwf_queue = NULL;
+
+static int print_nwf_error(void *log_ctx, nw_error_t error, int successRet, const char *func)
+{
+ if (!error)
+ return successRet;
+
+ switch (nw_error_get_error_domain(error)) {
+ case nw_error_domain_posix:
+ return AVERROR(nw_error_get_error_code(error));
+ default:
+ av_log(log_ctx, AV_LOG_ERROR, "[%s] Error domain %u, code %i\n", func,
+ nw_error_get_error_domain(error),
+ nw_error_get_error_code(error));
+ return AVERROR(EIO);
+ }
+}
+
+#define PRINT_NWF_ERROR(err, successRet) print_nwf_error(h, err, successRet, __FUNCTION__)
+
+static int ff_nwf_init(void)
+{
+ if (NWF_CHECK) {
+ int ret = 0;
+ ff_lock_avformat();
+ if (!nwf_init)
+ nwf_queue = dispatch_queue_create("org.ffmpeg.nwf", DISPATCH_QUEUE_SERIAL);
+ if (nwf_queue)
+ nwf_init++;
+ else
+ ret = AVERROR(ENOMEM);
+ ff_unlock_avformat();
+
+ return ret;
+ }
+
+ return AVERROR(EINVAL);
+}
+
+static void ff_nwf_deinit(void)
+{
+ if (NWF_CHECK) {
+ ff_lock_avformat();
+ nwf_init--;
+ if (!nwf_init)
+ dispatch_release(nwf_queue);
+ ff_unlock_avformat();
+ }
+}
+#endif
+
static int print_tls_error(URLContext *h, int ret)
{
TLSContext *c = h->priv_data;
@@ -272,6 +342,35 @@ static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size
static int tls_close(URLContext *h)
{
TLSContext *c = h->priv_data;
+
+#if CONFIG_NWF
+ if (NWF_CHECK) {
+ if (c->nw_conn) {
+ nw_connection_cancel(c->nw_conn);
+ nw_release(c->nw_conn);
+ }
+
+ pthread_mutex_lock(&c->state_lock);
+ while (c->nw_state != nw_connection_state_cancelled &&
+ c->nw_state != nw_connection_state_failed &&
+ c->nw_state != nw_connection_state_invalid) {
+ pthread_mutex_unlock(&c->state_lock);
+ dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+ pthread_mutex_lock(&c->state_lock);
+ }
+ pthread_mutex_unlock(&c->state_lock);
+
+ if (c->semaphore)
+ dispatch_release(c->semaphore);
+ if (c->state_semaphore)
+ dispatch_release(c->state_semaphore);
+
+ pthread_mutex_destroy(&c->state_lock);
+
+ ff_nwf_deinit();
+ }
+#endif
+
if (c->ssl_context) {
SSLClose(c->ssl_context);
CFRelease(c->ssl_context);
@@ -296,7 +395,235 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
{
TLSContext *c = h->priv_data;
TLSShared *s = &c->tls_shared;
- int ret;
+ int ret = 0;
+
+ if (s->ca_file) {
+ if ((ret = load_ca(h)) < 0)
+ goto fail;
+ }
+
+#if CONFIG_NWF
+ if (NWF_CHECK) {
+ int port;
+ char portStr[8];
+ SecIdentityRef identity = NULL;
+ CFArrayRef certArray = NULL;
+ nw_endpoint_t endpoint = NULL;
+ nw_parameters_t parameters = NULL;
+ bool finished = false;
+
+ if ((ret = ff_nwf_init()) < 0)
+ return ret;
+
+ if ((ret = AVERROR(pthread_mutex_init(&c->state_lock, NULL))) < 0)
+ goto nwf_fail;
+
+ if ((ret = ff_tls_process_underlying(s, h, uri, &port)) < 0)
+ goto nwf_fail;
+
+ snprintf(portStr, sizeof(portStr), "%i", port);
+
+ if (!(c->semaphore = dispatch_semaphore_create(0))) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+
+ if (!(c->state_semaphore = dispatch_semaphore_create(0))) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+
+ if (!(endpoint = nw_endpoint_create_host(s->underlying_host, portStr))) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+
+ if (s->cert_file && (ret = load_identity(h, &identity, &certArray)) < 0)
+ goto nwf_fail;
+
+ if (!(parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) {
+ sec_protocol_options_t options = nw_tls_copy_sec_protocol_options(tls_options);
+ sec_protocol_options_set_tls_server_name(options, s->host);
+ sec_protocol_options_set_peer_authentication_required(options, s->verify);
+ if (s->ca_file || !s->verify)
+ sec_protocol_options_set_verify_block(options, ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete) {
+ bool succ = false;
+ SecTrustRef peerTrust = NULL;
+ SecTrustResultType trustResult;
+
+ // set_peer_authentication_required is buggy; seems to no-op
+ if (!s->verify)
+ complete(true);
+
+ if (!(peerTrust = sec_trust_copy_ref(trust_ref)))
+ goto verify_fail;
+
+ if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr)
+ goto verify_fail;
+
+ if (SecTrustEvaluate(peerTrust, &trustResult) != noErr)
+ goto verify_fail;
+
+ succ = (trustResult == kSecTrustResultProceed ||
+ trustResult == kSecTrustResultUnspecified);
+
+verify_fail:
+ if (peerTrust)
+ CFRelease(peerTrust);
+ complete(succ);
+ }, nwf_queue);
+ if (identity) {
+ sec_identity_t sec_id = sec_identity_create_with_certificates(identity, certArray);
+ if (sec_id) {
+ sec_protocol_options_set_local_identity(options, sec_id);
+ nw_release(sec_id);
+ }
+ }
+ }, ^(nw_protocol_options_t tcp_options) {
+ nw_tcp_options_set_no_delay(tcp_options, c->tcp_nodelay);
+ }))) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+
+ if (s->listen) {
+ nw_listener_t listener;
+
+ nw_parameters_set_local_endpoint(parameters, endpoint);
+
+ listener = nw_listener_create(parameters);
+ if (!listener) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+
+ nw_listener_set_queue(listener, nwf_queue);
+
+ nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) {
+ pthread_mutex_lock(&c->state_lock);
+ if (!c->nw_conn) {
+ nw_retain(connection);
+ c->nw_conn = connection;
+ dispatch_semaphore_signal(c->state_semaphore);
+ }
+ pthread_mutex_unlock(&c->state_lock);
+ });
+ nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t error) {
+ pthread_mutex_lock(&c->state_lock);
+ c->nw_listen_state = state;
+ c->nw_state_error = error;
+ dispatch_semaphore_signal(c->state_semaphore);
+ pthread_mutex_unlock(&c->state_lock);
+ });
+
+ nw_listener_start(listener);
+
+ while (!finished) {
+ dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+ pthread_mutex_lock(&c->state_lock);
+ switch (c->nw_listen_state) {
+ case nw_listener_state_invalid:
+ ret = AVERROR_UNKNOWN;
+ finished = true;
+ break;
+ case nw_listener_state_waiting:
+ break;
+ case nw_listener_state_ready:
+ ret = 0;
+ if (c->nw_conn)
+ finished = true;
+ break;
+ case nw_listener_state_failed:
+ default:
+ ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN);
+ finished = true;
+ break;
+ case nw_listener_state_cancelled:
+ ret = AVERROR(EIO);
+ finished = true;
+ break;
+ }
+ pthread_mutex_unlock(&c->state_lock);
+ }
+
+ finished = false;
+
+ nw_listener_cancel(listener);
+ nw_release(listener);
+
+ pthread_mutex_lock(&c->state_lock);
+ while (c->nw_listen_state != nw_listener_state_cancelled &&
+ c->nw_listen_state != nw_listener_state_failed &&
+ c->nw_listen_state != nw_listener_state_invalid) {
+ pthread_mutex_unlock(&c->state_lock);
+ dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+ pthread_mutex_lock(&c->state_lock);
+ }
+ pthread_mutex_unlock(&c->state_lock);
+
+ if (!c->nw_conn)
+ goto nwf_fail;
+ } else {
+ if (!(c->nw_conn = nw_connection_create(endpoint, parameters))) {
+ ret = AVERROR(ENOMEM);
+ goto nwf_fail;
+ }
+ }
+
+ nw_connection_set_state_changed_handler(c->nw_conn, ^(nw_connection_state_t state, nw_error_t error) {
+ pthread_mutex_lock(&c->state_lock);
+ c->nw_state = state;
+ c->nw_state_error = error;
+ dispatch_semaphore_signal(c->state_semaphore);
+ pthread_mutex_unlock(&c->state_lock);
+ });
+
+ nw_connection_set_queue(c->nw_conn, nwf_queue);
+
+ nw_connection_start(c->nw_conn);
+
+ while (!finished) {
+ dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+ pthread_mutex_lock(&c->state_lock);
+ switch (c->nw_state) {
+ case nw_connection_state_invalid:
+ ret = AVERROR_UNKNOWN;
+ finished = true;
+ break;
+ case nw_connection_state_waiting:
+ case nw_connection_state_preparing:
+ break;
+ case nw_connection_state_ready:
+ ret = 0;
+ finished = true;
+ break;
+ case nw_connection_state_failed:
+ default:
+ ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN);
+ finished = true;
+ break;
+ case nw_connection_state_cancelled:
+ ret = AVERROR(EIO);
+ finished = true;
+ break;
+ }
+ pthread_mutex_unlock(&c->state_lock);
+ }
+
+nwf_fail:
+ if (endpoint)
+ nw_release(endpoint);
+ if (parameters)
+ nw_release(parameters);
+ if (certArray)
+ CFRelease(certArray);
+ if (identity)
+ CFRelease(identity);
+ if (ret < 0)
+ tls_close(h);
+ return ret;
+ }
+#endif
if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
goto fail;
@@ -307,10 +634,6 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
ret = AVERROR(ENOMEM);
goto fail;
}
- if (s->ca_file) {
- if ((ret = load_ca(h)) < 0)
- goto fail;
- }
if (s->ca_file || !s->verify)
CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
if (s->cert_file)
@@ -393,6 +716,41 @@ static int tls_read(URLContext *h, uint8_t *buf, int size)
TLSContext *c = h->priv_data;
size_t available = 0, processed = 0;
int ret;
+
+#if CONFIG_NWF
+ if (NWF_CHECK) {
+ __block nw_error_t error;
+ __block int gotSize = 0;
+
+ if (c->at_eof)
+ return AVERROR_EOF;
+
+ nw_connection_receive(c->nw_conn, 1, size, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t inError) {
+ if (is_complete)
+ c->at_eof = 1;
+
+ if (content) {
+ gotSize = dispatch_data_get_size(content);
+
+ dispatch_data_apply(content, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t inSize) {
+ memcpy(buf + offset, buffer, inSize);
+ return (bool)true;
+ });
+ }
+
+ error = inError;
+ dispatch_semaphore_signal(c->semaphore);
+ });
+
+ dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER);
+
+ if (c->at_eof && !gotSize && !error)
+ return AVERROR_EOF;
+
+ return PRINT_NWF_ERROR(error, gotSize);
+ }
+#endif
+
SSLGetBufferedReadSize(c->ssl_context, &available);
if (available)
size = FFMIN(available, size);
@@ -409,7 +767,25 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size)
{
TLSContext *c = h->priv_data;
size_t processed = 0;
- int ret = SSLWrite(c->ssl_context, buf, size, &processed);
+ int ret;
+
+#if CONFIG_NWF
+ if (NWF_CHECK) {
+ __block nw_error_t error;
+ dispatch_data_t content = dispatch_data_create(buf, size, nwf_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+ nw_connection_send(c->nw_conn, content, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t inError) {
+ error = inError;
+ dispatch_semaphore_signal(c->semaphore);
+ });
+ dispatch_release(content);
+
+ dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER);
+
+ return PRINT_NWF_ERROR(error, size);
+ }
+#endif
+
+ ret = SSLWrite(c->ssl_context, buf, size, &processed);
ret = map_ssl_error(ret, processed);
if (ret > 0)
return ret;
@@ -421,11 +797,22 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size)
static int tls_get_file_handle(URLContext *h)
{
TLSContext *c = h->priv_data;
+
+#if CONFIG_NWF
+ if (NWF_CHECK) // Network.framework doesn't expose file handles
+ return -1;
+#endif
+
return ffurl_get_file_handle(c->tls_shared.tcp);
}
+#define OFFSET(v) offsetof(TLSContext, v)
+
static const AVOption options[] = {
TLS_COMMON_OPTIONS(TLSContext, tls_shared),
+#if CONFIG_NWF
+ { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm", OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL },
+#endif
{ NULL }
};