PR #23051 opened by gnattu URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23051 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23051.patch
Add a new TLS backend using Apple Network.framework, which is the modern replacement for the deprecated SecureTransport. The existing SecureTransport backend is preserved as fallback for older macOS versions. When both are available, Network.framework takes priority. Signed-off-by: gnattu <[email protected]> This backend needs a slightly different design from the existing TLS backends because Apple Network.framework is not just a TLS layer. It is an end-to-end connection framework that owns TCP connection and the underlying socket lifecycle. Because of that, it is not compatible with the `ff_tls_open_underlying()` used by other backends, which first open and manage a lower-level TCP socket and then let the backend to layer TLS on top of it. This backend creates connections directly with Network.framework. This caused some side effects, the proxy support has to be implemented on Network.framework's own APIs, and we lost the access to lower level fds like other backends. This makes some use cases requires that like RTSP over TLS incompatible with this backend. Most of the logic for this backend is bridging async Network.framework operations to the synchronous blocking model that ffmpeg is expecting. Fixes #21686 >From 19c785ca7c254d5e865ab21dc45ef50305cd3c16 Mon Sep 17 00:00:00 2001 From: gnattu <[email protected]> Date: Tue, 5 May 2026 19:13:15 +0800 Subject: [PATCH] avformat/tls_networkframework: add Network.framework TLS backend Add a new TLS backend using Apple Network.framework, which is the modern replacement for the deprecated SecureTransport. The existing SecureTransport backend is preserved as fallback for older macOS versions. When both are available, Network.framework takes priority. Signed-off-by: gnattu <[email protected]> --- configure | 18 +- libavformat/Makefile | 1 + libavformat/avio.c | 2 +- libavformat/tls_networkframework.m | 994 +++++++++++++++++++++++++++++ 4 files changed, 1012 insertions(+), 3 deletions(-) create mode 100644 libavformat/tls_networkframework.m diff --git a/configure b/configure index 39a522e7e8..9a2a519883 100755 --- a/configure +++ b/configure @@ -340,8 +340,10 @@ External library support: --disable-schannel disable SChannel SSP, needed for TLS support on Windows if openssl and gnutls are not used [autodetect] --disable-sdl2 disable sdl2 [autodetect] + --disable-networkframework disable Apple Network.framework TLS, preferred + on macOS 10.15+ if openssl and gnutls are not used [autodetect] --disable-securetransport disable Secure Transport, needed for TLS support - on OSX if openssl and gnutls are not used [autodetect] + on macOS if openssl and gnutls are not used [autodetect] --enable-vapoursynth enable VapourSynth demuxer [no] --enable-whisper enable whisper filter [no] --disable-xlib disable xlib [autodetect] @@ -2022,6 +2024,7 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST=" lzma mediafoundation metal + networkframework schannel sdl2 securetransport @@ -4107,10 +4110,11 @@ rtp_protocol_select="udp_protocol" schannel_conflict="openssl gnutls libtls mbedtls" sctp_protocol_deps="struct_sctp_event_subscribe struct_msghdr_msg_flags" sctp_protocol_select="network" +networkframework_conflict="openssl gnutls libtls mbedtls" 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 openssl schannel securetransport networkframework libtls mbedtls" tls_protocol_select="tcp_protocol" # TODO: Support libtls. dtls_protocol_deps_any="openssl schannel gnutls mbedtls" @@ -7614,6 +7618,16 @@ if enabled decklink; then esac fi +enabled networkframework && + check_lib networkframework "Network/Network.h Security/Security.h" "nw_connection_create" "-framework Network -framework Security" || + disable networkframework + +# Prefer Network.framework over SecureTransport on modern macOS +enabled networkframework && disable securetransport + +enabled networkframework && + check_func SecItemImport "-framework CoreFoundation -framework Security" + enabled securetransport && check_func SecIdentityCreate "-framework CoreFoundation -framework Security" && check_lib securetransport "Security/SecureTransport.h Security/Security.h" "SSLCreateContext" "-framework CoreFoundation -framework Security" || diff --git a/libavformat/Makefile b/libavformat/Makefile index ac56e54aa8..11c8e91728 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -722,6 +722,7 @@ TLS-OBJS-$(CONFIG_GNUTLS) += tls_gnutls.o TLS-OBJS-$(CONFIG_LIBTLS) += tls_libtls.o TLS-OBJS-$(CONFIG_MBEDTLS) += tls_mbedtls.o TLS-OBJS-$(CONFIG_OPENSSL) += tls_openssl.o +TLS-OBJS-$(CONFIG_NETWORKFRAMEWORK) += tls_networkframework.o TLS-OBJS-$(CONFIG_SECURETRANSPORT) += tls_securetransport.o TLS-OBJS-$(CONFIG_SCHANNEL) += tls_schannel.o OBJS-$(CONFIG_TLS_PROTOCOL) += tls.o $(TLS-OBJS-yes) diff --git a/libavformat/avio.c b/libavformat/avio.c index 1e26092f79..e0f9b0690c 100644 --- a/libavformat/avio.c +++ b/libavformat/avio.c @@ -347,7 +347,7 @@ static const struct URLProtocol *url_find_protocol(const char *filename) if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL) || av_strstart(filename, "dtls:", NULL)) av_log(NULL, AV_LOG_WARNING, "https or dtls protocol not found, recompile FFmpeg with " - "openssl, gnutls or securetransport enabled.\n"); + "openssl, gnutls, networkframework or securetransport enabled.\n"); return NULL; } diff --git a/libavformat/tls_networkframework.m b/libavformat/tls_networkframework.m new file mode 100644 index 0000000000..1569ed2128 --- /dev/null +++ b/libavformat/tls_networkframework.m @@ -0,0 +1,994 @@ +/* + * Copyright (c) 2026 gnattu + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software * Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include "avformat.h" +#include "avio_internal.h" +#include "internal.h" +#include "network.h" +#include "os_support.h" +#include "url.h" +#include "tls.h" +#include "libavutil/avstring.h" +#include "libavutil/getenv_utf8.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/thread.h" +#include <Network/Network.h> +#include <Security/Security.h> +#include <CoreFoundation/CoreFoundation.h> +#include <dispatch/dispatch.h> + +static int import_pem(URLContext *h, const char *path, CFArrayRef *array) +{ +#if !HAVE_SECITEMIMPORT + return AVERROR_PATCHWELCOME; +#else + AVIOContext *s = NULL; + CFDataRef data = NULL; + int64_t ret = 0; + unsigned char *buf = NULL; + CFStringRef pathStr = NULL; + SecExternalFormat format = kSecFormatPEMSequence; + SecExternalItemType type = kSecItemTypeAggregate; + *array = NULL; + pathStr = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); + if (!pathStr) { + ret = AVERROR(ENOMEM); + goto end; + } + ret = ffio_open_whitelist(&s, path, AVIO_FLAG_READ, + &h->interrupt_callback, NULL, + h->protocol_whitelist, h->protocol_blacklist); + if (ret < 0) + goto end; + ret = avio_size(s); + if (ret < 0) + goto end; + if (ret == 0) { + ret = AVERROR_INVALIDDATA; + goto end; + } + buf = av_malloc(ret); + if (!buf) { + ret = AVERROR(ENOMEM); + goto end; + } + ret = avio_read(s, buf, (int)ret); + if (ret < 0) + goto end; + data = CFDataCreate(kCFAllocatorDefault, buf, (CFIndex)ret); + + if (SecItemImport(data, pathStr, &format, &type, + 0, NULL, NULL, array) != noErr || !*array) { + ret = AVERROR_UNKNOWN; + goto end; + } + + if (CFArrayGetCount(*array) == 0) { + CFRelease(*array); + *array = NULL; + ret = AVERROR_INVALIDDATA; + goto end; + } +end: + av_free(buf); + if (pathStr) + CFRelease(pathStr); + if (data) + CFRelease(data); + if (s) + avio_close(s); + return (int)ret; +#endif +} +typedef struct TLSContext { + TLSShared tls_shared; + AVMutex lock; + int lock_initialized; + int closing; + nw_listener_t listener; + nw_connection_t connection; + dispatch_queue_t queue; + dispatch_semaphore_t listener_sem; + dispatch_semaphore_t ready_sem; + dispatch_semaphore_t recv_sem; + dispatch_semaphore_t send_sem; + dispatch_data_t recv_data; + size_t recv_offset; + bool recv_complete; + int connected; + int errored; + int eof; + int lastErr; + uintptr_t conn_generation; + CFArrayRef ca_array; + sec_identity_t local_identity; +} TLSContext; + +static int tls_init_lock(TLSContext *c) +{ + int ret; + + if (c->lock_initialized) + return 0; + + ret = ff_mutex_init(&c->lock, NULL); + if (ret) + return AVERROR(ret); + + c->lock_initialized = 1; + return 0; +} + +static void tls_abort_connection(TLSContext *c) +{ + nw_connection_t connection = NULL; + dispatch_semaphore_t ready_sem = NULL; + dispatch_semaphore_t listener_sem = NULL; + dispatch_semaphore_t recv_sem = NULL; + dispatch_semaphore_t send_sem = NULL; + + ff_mutex_lock(&c->lock); + c->closing = 1; + c->errored = 1; + c->lastErr = AVERROR_EXIT; + connection = c->connection; + ready_sem = c->ready_sem; + listener_sem = c->listener_sem; + recv_sem = c->recv_sem; + send_sem = c->send_sem; + if (connection) + nw_retain(connection); + if (ready_sem) + dispatch_retain(ready_sem); + if (listener_sem) + dispatch_retain(listener_sem); + if (recv_sem) + dispatch_retain(recv_sem); + if (send_sem) + dispatch_retain(send_sem); + ff_mutex_unlock(&c->lock); + + if (ready_sem) + dispatch_semaphore_signal(ready_sem); + if (listener_sem) + dispatch_semaphore_signal(listener_sem); + if (recv_sem) + dispatch_semaphore_signal(recv_sem); + if (send_sem) + dispatch_semaphore_signal(send_sem); + if (connection) { + nw_connection_cancel(connection); + nw_release(connection); + } + if (ready_sem) + dispatch_release(ready_sem); + if (listener_sem) + dispatch_release(listener_sem); + if (recv_sem) + dispatch_release(recv_sem); + if (send_sem) + dispatch_release(send_sem); +} + +static void log_nw_error(URLContext *h, const char *what, nw_error_t error) +{ + if (error) { + av_log(h, AV_LOG_ERROR, "%s: domain=%d code=%d\n", what, + (int)nw_error_get_error_domain(error), + (int)nw_error_get_error_code(error)); + } else { + av_log(h, AV_LOG_ERROR, "%s\n", what); + } +} + +// Network.framework APIs are callback-based, but ffmpeg expects synchronous blocking reads/writes. +// Make I/O operation dispatches an async call then blocks on a semaphore untill the callback finished. +static int wait_for_semaphore(URLContext *h, dispatch_semaphore_t sem) +{ + TLSContext *c = h->priv_data; + + while (1) { + if (ff_check_interrupt(&h->interrupt_callback)) { + tls_abort_connection(c); + return AVERROR_EXIT; + } + if (dispatch_semaphore_wait(sem, + dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC)) == 0) + break; + } + + if (ff_check_interrupt(&h->interrupt_callback)) { + tls_abort_connection(c); + return AVERROR_EXIT; + } + + return 0; +} + +static void install_connection_state_handler(URLContext *h, TLSContext *c) +{ + uintptr_t gen = ++c->conn_generation; + + nw_connection_set_state_changed_handler(c->connection, + ^(nw_connection_state_t state, nw_error_t error) { + dispatch_semaphore_t state_ready_sem = NULL; + + ff_mutex_lock(&c->lock); + if (c->conn_generation != gen) + goto end; + state_ready_sem = c->ready_sem; + if (state_ready_sem) + dispatch_retain(state_ready_sem); + switch (state) { + case nw_connection_state_ready: + c->connected = 1; + break; + case nw_connection_state_failed: + if (!(c->closing && c->lastErr == AVERROR_EXIT)) { + c->errored = 1; + c->lastErr = AVERROR(EIO); + log_nw_error(h, c->tls_shared.listen ? "TLS server connection failed" + : "TLS connection failed", + error); + } + break; + case nw_connection_state_cancelled: + default: + break; + } +end: + ff_mutex_unlock(&c->lock); + if (state_ready_sem) { + if (state == nw_connection_state_ready || state == nw_connection_state_failed) + dispatch_semaphore_signal(state_ready_sem); + dispatch_release(state_ready_sem); + } + }); +} +static int tls_close(URLContext *h) +{ + TLSContext *c = h->priv_data; + nw_listener_t listener = NULL; + nw_connection_t connection = NULL; + dispatch_queue_t queue = NULL; + dispatch_data_t recv_data = NULL; + dispatch_semaphore_t ready_sem = NULL; + dispatch_semaphore_t listener_sem = NULL; + dispatch_semaphore_t recv_sem = NULL; + dispatch_semaphore_t send_sem = NULL; + + if (c->lock_initialized) + ff_mutex_lock(&c->lock); + + c->closing = 1; + if (!c->connected && !c->errored) { + c->errored = 1; + c->lastErr = AVERROR_EXIT; + } + c->errored = 1; + if (!c->lastErr) + c->lastErr = AVERROR_EXIT; + + listener = c->listener; + connection = c->connection; + queue = c->queue; + recv_data = c->recv_data; + ready_sem = c->ready_sem; + listener_sem = c->listener_sem; + recv_sem = c->recv_sem; + send_sem = c->send_sem; + + c->listener = NULL; + c->connection = NULL; + c->queue = NULL; + c->recv_data = NULL; + c->ready_sem = NULL; + c->listener_sem = NULL; + c->recv_sem = NULL; + c->send_sem = NULL; + c->recv_offset = 0; + c->recv_complete = false; + + if (ready_sem) + dispatch_retain(ready_sem); + if (listener_sem) + dispatch_retain(listener_sem); + if (recv_sem) + dispatch_retain(recv_sem); + if (send_sem) + dispatch_retain(send_sem); + + if (c->lock_initialized) + ff_mutex_unlock(&c->lock); + + if (ready_sem) + dispatch_semaphore_signal(ready_sem); + if (listener_sem) + dispatch_semaphore_signal(listener_sem); + if (recv_sem) + dispatch_semaphore_signal(recv_sem); + if (send_sem) + dispatch_semaphore_signal(send_sem); + + if (listener) + nw_listener_cancel(listener); + if (connection) + nw_connection_cancel(connection); + if (queue) + dispatch_barrier_sync(queue, ^{}); + + if (listener) + nw_release(listener); + if (connection) + nw_release(connection); + if (queue) + dispatch_release(queue); + if (recv_data) + dispatch_release(recv_data); + + // Each detached semaphore carries two retains here: its original ownership from + // c->*_sem and the temporary retain taken above so it requires two releases + if (ready_sem) { + dispatch_release(ready_sem); + dispatch_release(ready_sem); + } + if (listener_sem) { + dispatch_release(listener_sem); + dispatch_release(listener_sem); + } + if (recv_sem) { + dispatch_release(recv_sem); + dispatch_release(recv_sem); + } + if (send_sem) { + dispatch_release(send_sem); + dispatch_release(send_sem); + } + if (c->ca_array) { + CFRelease(c->ca_array); + c->ca_array = NULL; + } + if (c->local_identity) { + sec_release(c->local_identity); + c->local_identity = NULL; + } + ffurl_closep(&c->tls_shared.tcp); + return 0; +} +static void configure_tls_options(sec_protocol_options_t sopts, + TLSShared *s, TLSContext *c) +{ + sec_protocol_options_set_min_tls_protocol_version(sopts, + tls_protocol_version_TLSv12); + if (s->listen) + sec_protocol_options_set_peer_authentication_required(sopts, s->verify); + if (!s->verify) + sec_protocol_options_set_verify_block(sopts, ^(sec_protocol_metadata_t meta, + sec_trust_t trust, sec_protocol_verify_complete_t complete) { + complete(true); + }, c->queue); + if (s->verify && c->ca_array) + sec_protocol_options_set_verify_block(sopts, ^(sec_protocol_metadata_t meta, + sec_trust_t trust, sec_protocol_verify_complete_t complete) { + SecTrustRef t = sec_trust_copy_ref(trust); + bool trusted = false; + if (t) { + SecTrustSetAnchorCertificates(t, c->ca_array); + trusted = SecTrustEvaluateWithError(t, NULL); + CFRelease(t); + } + complete(trusted); + }, c->queue); + if (c->local_identity) + sec_protocol_options_set_local_identity(sopts, c->local_identity); + if (!s->listen && s->host && s->host[0]) + sec_protocol_options_set_tls_server_name(sopts, s->host); +} +static int process_buffered_data(uint8_t *buf, int size, TLSContext *c) +{ + const void *data_buf = NULL; + size_t data_len = 0; + dispatch_data_t recv_data = NULL; + dispatch_data_t map; + + ff_mutex_lock(&c->lock); + recv_data = c->recv_data; + if (recv_data) + dispatch_retain(recv_data); + ff_mutex_unlock(&c->lock); + + if (!recv_data) + return -1; + + map = dispatch_data_create_map(recv_data, &data_buf, &data_len); + if (!map || !data_buf) { + dispatch_release(recv_data); + if (map) dispatch_release(map); + return -1; + } + ff_mutex_lock(&c->lock); + if (c->recv_data != recv_data) { + ff_mutex_unlock(&c->lock); + dispatch_release(map); + dispatch_release(recv_data); + return -1; + } + size_t avail = data_len - c->recv_offset; + size_t to_copy = avail > (size_t)size ? (size_t)size : avail; + memcpy(buf, (const uint8_t *)data_buf + c->recv_offset, to_copy); + dispatch_release(map); + if (c->recv_offset + to_copy >= data_len) { + dispatch_release(c->recv_data); + c->recv_data = NULL; + c->recv_offset = 0; + if (c->recv_complete) + c->eof = 1; + } else { + c->recv_offset += to_copy; + } + ff_mutex_unlock(&c->lock); + dispatch_release(recv_data); + return (int)to_copy; +} +static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options) +{ + TLSContext *c = h->priv_data; + TLSShared *s = &c->tls_shared; + int ret; + char host[200], port_str[16]; + int port; + nw_parameters_t parameters = NULL; + nw_privacy_context_t privacy_ctx = NULL; + dispatch_semaphore_t listener_sem = NULL; + dispatch_semaphore_t ready_sem = NULL; + + ret = tls_init_lock(c); + if (ret < 0) + return ret; + { + const char *p = strchr(uri, '?'); + if (p) { + ret = ff_parse_opts_from_query_string(s, p, 1); + if (ret < 0) + return ret; + } + } + + ff_mutex_lock(&c->lock); + if (c->closing) { + ff_mutex_unlock(&c->lock); + return c->lastErr ? c->lastErr : AVERROR_EXIT; + } + c->connected = c->errored = c->eof = c->lastErr = 0; + if (c->recv_data) { + dispatch_release(c->recv_data); + c->recv_data = NULL; + } + c->recv_offset = 0; + c->recv_complete = false; + ff_mutex_unlock(&c->lock); + av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, NULL, 0, uri); + if (!host[0] && !s->listen) { + ret = AVERROR(EINVAL); + goto fail; + } + if (port <= 0) + port = 443; + if (host[0] && !s->host && !(s->host = av_strdup(host))) { + ret = AVERROR(ENOMEM); + goto fail; + } + c->queue = dispatch_queue_create("org.ffmpeg.tls.nw", DISPATCH_QUEUE_SERIAL); + if (!c->queue) { + ret = AVERROR(ENOMEM); + goto fail; + } + c->listener_sem = dispatch_semaphore_create(0); + c->ready_sem = dispatch_semaphore_create(0); + c->recv_sem = dispatch_semaphore_create(0); + c->send_sem = dispatch_semaphore_create(0); + listener_sem = c->listener_sem; + ready_sem = c->ready_sem; + if (listener_sem) + dispatch_retain(listener_sem); + if (ready_sem) + dispatch_retain(ready_sem); + if (s->ca_file) { + ret = import_pem(h, s->ca_file, &c->ca_array); + if (ret < 0) + goto fail; + } + if (s->cert_file && s->key_file) { + CFArrayRef cert_array = NULL, key_array = NULL; + ret = import_pem(h, s->cert_file, &cert_array); + if (ret < 0) + goto fail; + ret = import_pem(h, s->key_file, &key_array); + if (ret < 0) { + CFRelease(cert_array); + goto fail; + } + SecIdentityRef identity = SecIdentityCreate(kCFAllocatorDefault, + (SecCertificateRef)CFArrayGetValueAtIndex(cert_array, 0), + (SecKeyRef)CFArrayGetValueAtIndex(key_array, 0)); + CFRelease(key_array); + if (!identity) { + CFRelease(cert_array); + ret = AVERROR(EINVAL); + goto fail; + } + c->local_identity = sec_identity_create_with_certificates(identity, cert_array); + CFRelease(identity); + CFRelease(cert_array); + if (!c->local_identity) { + ret = AVERROR(EINVAL); + goto fail; + } + } else if (s->listen) { + av_log(h, AV_LOG_ERROR, "TLS listen mode requires cert_file and key_file\n"); + ret = AVERROR(EINVAL); + goto fail; + } + // Set up proxy if configured (macOS 14.0+) + if (!s->listen) { + if (@available(macOS 14.0, *)) { + char *env_http_proxy = getenv_utf8("http_proxy"); + char *env_no_proxy = getenv_utf8("no_proxy"); + const char *proxy_url = s->http_proxy ? s->http_proxy : env_http_proxy; + int is_socks = 0, use_proxy = 0; + + if (proxy_url) { + if (av_strstart(proxy_url, "http://", NULL)) + use_proxy = 1; + else if (av_strstart(proxy_url, "socks5://", NULL) || + av_strstart(proxy_url, "socks://", NULL)) { + use_proxy = 1; + is_socks = 1; + } + } + if (env_no_proxy && use_proxy) + use_proxy = !ff_http_match_no_proxy(env_no_proxy, host); + if (use_proxy) { + char proxy_host[200], proxy_auth[200], proxy_proto[16]; + int proxy_port; + + av_url_split(proxy_proto, sizeof(proxy_proto), + proxy_auth, sizeof(proxy_auth), + proxy_host, sizeof(proxy_host), &proxy_port, + NULL, 0, proxy_url); + if (proxy_port <= 0) + proxy_port = is_socks ? 1080 : 80; + + char proxy_port_str[16]; + snprintf(proxy_port_str, sizeof(proxy_port_str), "%d", proxy_port); + nw_endpoint_t proxy_ep = nw_endpoint_create_host(proxy_host, proxy_port_str); + nw_proxy_config_t proxy_cfg = is_socks + ? nw_proxy_config_create_socksv5(proxy_ep) + : nw_proxy_config_create_http_connect(proxy_ep, NULL); + nw_release(proxy_ep); + if (proxy_auth[0]) { + char *colon = strchr(proxy_auth, ':'); + if (colon) { + *colon = '\0'; + nw_proxy_config_set_username_and_password(proxy_cfg, + proxy_auth, colon + 1); + } else { + nw_proxy_config_set_username_and_password(proxy_cfg, + proxy_auth, ""); + } + } + privacy_ctx = nw_privacy_context_create("org.ffmpeg.tls"); + nw_privacy_context_add_proxy(privacy_ctx, proxy_cfg); + nw_release(proxy_cfg); + } + freeenv_utf8(env_no_proxy); + freeenv_utf8(env_http_proxy); + } + } + snprintf(port_str, sizeof(port_str), "%d", port); + parameters = nw_parameters_create_secure_tcp( + ^(nw_protocol_options_t opts) { configure_tls_options( + nw_tls_copy_sec_protocol_options(opts), s, c); }, + NW_PARAMETERS_DEFAULT_CONFIGURATION); + if (!parameters) { + ret = AVERROR(ENOMEM); + goto fail; + } + if (privacy_ctx) { + nw_parameters_set_privacy_context(parameters, privacy_ctx); + nw_release(privacy_ctx); + privacy_ctx = NULL; + } + if (s->listen) { + if (host[0]) { + nw_endpoint_t local_endpoint = nw_endpoint_create_host(host, port_str); + if (!local_endpoint) { + ret = AVERROR(ENOMEM); + goto fail; + } + nw_parameters_set_local_endpoint(parameters, local_endpoint); + nw_release(local_endpoint); + c->listener = nw_listener_create(parameters); + } else { + c->listener = nw_listener_create_with_port(port_str, parameters); + } + nw_release(parameters); + parameters = NULL; + if (!c->listener) { + av_log(h, AV_LOG_ERROR, "Unable to create nw_listener\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + + nw_listener_set_queue(c->listener, c->queue); + nw_listener_set_state_changed_handler(c->listener, + ^(nw_listener_state_t state, nw_error_t error) { + dispatch_semaphore_t state_listener_sem = NULL; + dispatch_semaphore_t state_ready_sem = NULL; + + ff_mutex_lock(&c->lock); + state_listener_sem = c->listener_sem; + state_ready_sem = c->ready_sem; + if (state_listener_sem) + dispatch_retain(state_listener_sem); + if (state_ready_sem) + dispatch_retain(state_ready_sem); + switch (state) { + case nw_listener_state_ready: + break; + case nw_listener_state_failed: + if (!(c->closing && c->lastErr == AVERROR_EXIT)) { + c->errored = 1; + c->lastErr = AVERROR(EIO); + log_nw_error(h, "TLS listener failed", error); + } + break; + case nw_listener_state_cancelled: + if (!c->connected) { + c->errored = 1; + if (!c->lastErr) + c->lastErr = AVERROR(EIO); + } + break; + default: + break; + } + ff_mutex_unlock(&c->lock); + if (state_listener_sem) { + if (state != nw_listener_state_invalid) + dispatch_semaphore_signal(state_listener_sem); + dispatch_release(state_listener_sem); + } + if (state_ready_sem) { + if (state == nw_listener_state_failed || state == nw_listener_state_cancelled) + dispatch_semaphore_signal(state_ready_sem); + dispatch_release(state_ready_sem); + } + }); + nw_listener_set_new_connection_handler(c->listener, ^(nw_connection_t connection) { + ff_mutex_lock(&c->lock); + if (c->connection || c->errored || c->closing) { + ff_mutex_unlock(&c->lock); + nw_connection_cancel(connection); + return; + } + c->connection = nw_retain(connection); + nw_connection_set_queue(c->connection, c->queue); + install_connection_state_handler(h, c); + nw_connection_start(c->connection); + ff_mutex_unlock(&c->lock); + }); + nw_listener_start(c->listener); + + ret = wait_for_semaphore(h, listener_sem); + if (ret < 0) + goto fail; + } else { + nw_endpoint_t endpoint = nw_endpoint_create_host(host, port_str); + if (!endpoint) { + ret = AVERROR(ENOMEM); + goto fail; + } + c->connection = nw_connection_create(endpoint, parameters); + nw_release(endpoint); + nw_release(parameters); + parameters = NULL; + if (!c->connection) { + av_log(h, AV_LOG_ERROR, "Unable to create nw_connection\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + nw_connection_set_queue(c->connection, c->queue); + install_connection_state_handler(h, c); + nw_connection_start(c->connection); + } + + ret = wait_for_semaphore(h, ready_sem); + if (ret < 0) + goto fail; + ff_mutex_lock(&c->lock); + if (c->errored || !c->connected) + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + ff_mutex_unlock(&c->lock); + if (ret < 0) + goto fail; + dispatch_release(listener_sem); + dispatch_release(ready_sem); + return 0; +fail: + if (listener_sem) + dispatch_release(listener_sem); + if (ready_sem) + dispatch_release(ready_sem); + if (parameters) + nw_release(parameters); + if (privacy_ctx) + nw_release(privacy_ctx); + tls_close(h); + return ret; +} +static int tls_read(URLContext *h, uint8_t *buf, int size) +{ + TLSContext *c = h->priv_data; + nw_connection_t connection = NULL; + dispatch_semaphore_t recv_sem = NULL; + int ret = 0; + + ff_mutex_lock(&c->lock); + if (c->errored || c->closing) { + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + ff_mutex_unlock(&c->lock); + return ret; + } + if (c->eof) { + ff_mutex_unlock(&c->lock); + return AVERROR_EOF; + } + connection = c->connection; + recv_sem = c->recv_sem; + if (connection) + nw_retain(connection); + if (recv_sem) + dispatch_retain(recv_sem); + ff_mutex_unlock(&c->lock); + + if (!connection || !recv_sem) { + if (connection) + nw_release(connection); + if (recv_sem) + dispatch_release(recv_sem); + return c->lastErr ? c->lastErr : AVERROR(EIO); + } + + ff_mutex_lock(&c->lock); + if (c->recv_data) + ret = 1; + else if (c->eof) + ret = AVERROR_EOF; + else + ret = 0; + ff_mutex_unlock(&c->lock); + if (ret == 1) { + ret = process_buffered_data(buf, size, c); + if (ret >= 0) + goto done; + } + if (ret == AVERROR_EOF) { + goto done; + } + nw_connection_receive(connection, 1, (uint32_t)size, + ^(dispatch_data_t content, nw_content_context_t ctx, + bool is_complete, nw_error_t error) { + dispatch_semaphore_t callback_recv_sem; + + ff_mutex_lock(&c->lock); + if (error && !(c->closing && c->lastErr == AVERROR_EXIT)) { + c->errored = 1; + c->lastErr = AVERROR(EIO); + } + if (content) { + if (c->recv_data) + dispatch_release(c->recv_data); + dispatch_retain(content); + c->recv_data = content; + c->recv_offset = 0; + } + c->recv_complete = is_complete; + if (is_complete && !content) + c->eof = 1; + callback_recv_sem = c->recv_sem; + if (callback_recv_sem) + dispatch_retain(callback_recv_sem); + ff_mutex_unlock(&c->lock); + if (callback_recv_sem) { + dispatch_semaphore_signal(callback_recv_sem); + dispatch_release(callback_recv_sem); + } + }); + while (1) { + if (ff_check_interrupt(&h->interrupt_callback)) { + tls_abort_connection(c); + break; + } + if (dispatch_semaphore_wait(recv_sem, + dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC)) == 0) + break; + } + if (ff_check_interrupt(&h->interrupt_callback)) { + ret = AVERROR_EXIT; + goto done; + } + ff_mutex_lock(&c->lock); + if (c->errored) + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + else if (c->recv_data) + ret = 1; + else if (c->eof) + ret = AVERROR_EOF; + else + ret = AVERROR(EAGAIN); + ff_mutex_unlock(&c->lock); + if (ret < 0 && ret != AVERROR_EOF) + goto done; + if (ret == 1) { + ret = process_buffered_data(buf, size, c); + if (ret >= 0) + goto done; + } + if (ret == 1) + ret = AVERROR(EAGAIN); +done: + nw_release(connection); + dispatch_release(recv_sem); + return ret; +} +static int tls_write(URLContext *h, const uint8_t *buf, int size) +{ + TLSContext *c = h->priv_data; + nw_connection_t connection = NULL; + dispatch_queue_t queue = NULL; + dispatch_semaphore_t send_sem = NULL; + __block int send_err = 0; + int ret = 0; + + ff_mutex_lock(&c->lock); + if (c->errored || c->closing) { + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + ff_mutex_unlock(&c->lock); + return ret; + } + connection = c->connection; + queue = c->queue; + send_sem = c->send_sem; + if (connection) + nw_retain(connection); + if (queue) + dispatch_retain(queue); + if (send_sem) + dispatch_retain(send_sem); + ff_mutex_unlock(&c->lock); + + if (!connection || !queue || !send_sem) { + if (connection) + nw_release(connection); + if (queue) + dispatch_release(queue); + if (send_sem) + dispatch_release(send_sem); + return c->lastErr ? c->lastErr : AVERROR(EIO); + } + + ff_mutex_lock(&c->lock); + if (c->errored) + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + ff_mutex_unlock(&c->lock); + if (ret < 0) + goto done; + if (size <= 0) + goto done; + dispatch_data_t data = dispatch_data_create(buf, (size_t)size, + queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + if (!data) { + ret = AVERROR(ENOMEM); + goto done; + } + nw_connection_send(connection, data, + NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, + ^(nw_error_t error) { + dispatch_semaphore_t callback_send_sem; + + ff_mutex_lock(&c->lock); + if (error && !(c->closing && c->lastErr == AVERROR_EXIT)) { + c->errored = 1; + c->lastErr = AVERROR(EIO); + send_err = -1; + } + callback_send_sem = c->send_sem; + if (callback_send_sem) + dispatch_retain(callback_send_sem); + ff_mutex_unlock(&c->lock); + if (callback_send_sem) { + dispatch_semaphore_signal(callback_send_sem); + dispatch_release(callback_send_sem); + } + }); + dispatch_release(data); + while (1) { + if (ff_check_interrupt(&h->interrupt_callback)) { + tls_abort_connection(c); + break; + } + if (dispatch_semaphore_wait(send_sem, + dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC)) == 0) + break; + } + if (ff_check_interrupt(&h->interrupt_callback)) { + ret = AVERROR_EXIT; + goto done; + } + ff_mutex_lock(&c->lock); + if (send_err || c->errored) + ret = c->lastErr ? c->lastErr : AVERROR(EIO); + ff_mutex_unlock(&c->lock); + if (ret < 0) + goto done; + ret = size; +done: + if (connection) + nw_release(connection); + if (queue) + dispatch_release(queue); + if (send_sem) + dispatch_release(send_sem); + return ret; +} +static int tls_get_file_handle(URLContext *h) +{ + return AVERROR(ENOSYS); +} +static int tls_get_short_seek(URLContext *h) +{ + return AVERROR(ENOSYS); +} +static const AVOption options[] = { + TLS_COMMON_OPTIONS(TLSContext, tls_shared), + { NULL } +}; +static const AVClass tls_class = { + .class_name = "tls", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; +const URLProtocol ff_tls_protocol = { + .name = "tls", + .url_open2 = tls_open, + .url_read = tls_read, + .url_write = tls_write, + .url_close = tls_close, + .url_get_file_handle = tls_get_file_handle, + .url_get_short_seek = tls_get_short_seek, + .priv_data_size = sizeof(TLSContext), + .flags = URL_PROTOCOL_FLAG_NETWORK, + .priv_data_class = &tls_class, +}; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
