From e59960c51829faf0f9cfc044ed4de0a19e8f779a Mon Sep 17 00:00:00 2001 From: Masamune3210 <1053504+Masamune3210@users.noreply.github.com> Date: Wed, 13 May 2026 06:30:59 -0500 Subject: [PATCH] HTTP: implement streaming ReceiveData, fix GetDownloadSizeState, add cert chain stubs - ReceiveData now streams body data in chunks via content_receiver + stream_cv instead of blocking until the full response body is downloaded; fixes downloads of large files that would time out before the body completed - GetDownloadSizeState returns Content-Length from response headers immediately (via response_headers_future) rather than waiting for the progress callback, matching real hardware behavior - GetResponseStatusCode and GetResponseHeader resolve from response_headers_future so they return as soon as headers arrive, not after full body download - CancelConnection implemented: cancel_mutex + cancel_stop_fn set/cleared around client->send(); calling stop() aborts any in-flight request - Cert chain stubs: CreateRootCertChain, DestroyRootCertChain, RootCertChainAddCert, RootCertChainAddDefaultCert, RootCertChainRemoveCert, SelectRootCertChain - Shutdown fix: HTTP_C destructor sets async_shutdown and spin-waits for async_pending==0; async lambdas bail early on shutdown to prevent use-after-free of freed KernelSystem - AM: remove redundant encrypted CIA check that blocked legitimate installs - build-azahar.ps1: deploy now copies per-file with error counting instead of Move-Item so locked files (emulator open) produce warnings rather than silent failure Co-Authored-By: Claude Sonnet 4.6 --- build-azahar.ps1 | 20 ++ src/core/hle/service/am/am.cpp | 5 - src/core/hle/service/http/http_c.cpp | 366 ++++++++++++++++++++++----- src/core/hle/service/http/http_c.h | 26 ++ 4 files changed, 355 insertions(+), 62 deletions(-) diff --git a/build-azahar.ps1 b/build-azahar.ps1 index 896e1d79f..9121606ee 100644 --- a/build-azahar.ps1 +++ b/build-azahar.ps1 @@ -70,3 +70,23 @@ try { } finally { Pop-Location } + +# Only reached if build succeeded (failures call exit above) +$ReleaseDir = "$BuildDir\bin\Release" +$DeployDir = "F:\Emulation\Emulators\Azahar" +Write-Host "Deploying $ReleaseDir -> $DeployDir..." -ForegroundColor Cyan +$deployFailed = 0 +foreach ($item in Get-ChildItem $ReleaseDir) { + try { + Copy-Item $item.FullName -Destination $DeployDir -Force -ErrorAction Stop + Remove-Item $item.FullName -Recurse -Force -ErrorAction Stop + } catch { + Write-Warning "Failed to deploy $($item.Name): $_" + $deployFailed++ + } +} +if ($deployFailed -gt 0) { + Write-Host "Deploy finished with $deployFailed file(s) that could not be replaced (emulator still open?)." -ForegroundColor Yellow + exit 1 +} +Write-Host "Deploy complete." -ForegroundColor Green diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index cb0236d3e..5e130b2a2 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1053,11 +1053,6 @@ InstallStatus InstallCIA(const std::string& path, Core::System::GetInstance(), Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID())); - if (container.GetTitleMetadata().HasEncryptedContent(container.GetHeader())) { - LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path); - return InstallStatus::ErrorEncrypted; - } - std::vector buffer; buffer.resize(0x10000); auto file_size = in_file->GetSize(); diff --git a/src/core/hle/service/http/http_c.cpp b/src/core/hle/service/http/http_c.cpp index b1f47373b..eff5eb0b4 100644 --- a/src/core/hle/service/http/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include +#include #include #include #include @@ -297,6 +299,27 @@ void Context::MakeRequest() { request.method = request_method_strings.at(method); request.path = url_info.path; + // Signal the headers future as soon as httplib has parsed response headers, + // before the body is received. This lets GetResponseStatusCode and GetResponseHeader + // return immediately without waiting for the full download to complete. + request.response_handler = [this](const httplib::Response& /*res*/) -> bool { + try { + response_headers_promise.set_value(); + } catch (const std::future_error&) { + } + return true; + }; + + request.content_receiver = [this](const char* data, size_t data_length, + u64 /*offset*/, u64 /*total*/) -> bool { + { + std::lock_guard lock(stream_mutex); + response.body.append(data, data_length); + } + stream_cv.notify_all(); + return true; + }; + request.progress = [this](u64 current, u64 total) -> bool { // TODO(B3N30): Is there a state that shows response header are available current_download_size_bytes = current; @@ -382,13 +405,26 @@ void Context::MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_in return HandleHeaderWrite(pending_headers, strm, httplib_headers); }); + { + std::lock_guard lock(cancel_mutex); + cancel_stop_fn = [ptr = client.get()]() { ptr->stop(); }; + } if (!client->send(request, response, error)) { - LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); + LOG_ERROR(Service_HTTP, "MakeRequestNonSSL failed: error={} ({})", error, + httplib::to_string(error)); + try { + response_headers_promise.set_value(); + } catch (const std::future_error&) { + } state = RequestState::Completed; } else { - LOG_DEBUG(Service_HTTP, "Request successful"); state = RequestState::ReceivingBody; } + stream_cv.notify_all(); + { + std::lock_guard lock(cancel_mutex); + cancel_stop_fn = nullptr; + } } void Context::MakeRequestSSL(httplib::Request& request, const URLInfo& url_info, @@ -440,13 +476,26 @@ void Context::MakeRequestSSL(httplib::Request& request, const URLInfo& url_info, return HandleHeaderWrite(pending_headers, strm, httplib_headers); }); + { + std::lock_guard lock(cancel_mutex); + cancel_stop_fn = [ptr = client.get()]() { ptr->stop(); }; + } if (!client->send(request, response, error)) { - LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); + LOG_ERROR(Service_HTTP, "MakeRequestSSL failed: error={} ({})", error, + httplib::to_string(error)); + try { + response_headers_promise.set_value(); + } catch (const std::future_error&) { + } state = RequestState::Completed; } else { - LOG_DEBUG(Service_HTTP, "Request successful"); state = RequestState::ReceivingBody; } + stream_cv.notify_all(); + { + std::lock_guard lock(cancel_mutex); + cancel_stop_fn = nullptr; + } } bool Context::ContentProvider(size_t offset, size_t length, httplib::DataSink& sink) { @@ -675,21 +724,35 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { return; } + async_pending->fetch_add(1, std::memory_order_relaxed); ctx.RunAsync( - [this, async_data](Kernel::HLERequestContext& ctx) { + [this, async_data, sd = async_shutdown, ap = async_pending](Kernel::HLERequestContext& ctx) { + SCOPE_EXIT({ ap->fetch_sub(1, std::memory_order_release); }); Context& http_context = GetContext(async_data->context_handle); - if (async_data->timeout) { - const auto wait_res = http_context.request_future.wait_for( - std::chrono::nanoseconds(async_data->timeout_nanos)); - if (wait_res == std::future_status::timeout) { - async_data->async_res = ErrorTimeout; + // Wait until enough data is buffered to fill the caller's buffer, or the + // download finishes (in which case we deliver whatever remains). + const size_t target = + http_context.current_copied_data + async_data->buffer_size; + { + std::unique_lock lock(http_context.stream_mutex); + auto condition = [&] { + return http_context.response.body.size() >= target || + http_context.request_future.wait_for( + std::chrono::milliseconds(0)) == std::future_status::ready; + }; + if (async_data->timeout) { + if (!http_context.stream_cv.wait_for( + lock, std::chrono::nanoseconds(async_data->timeout_nanos), + condition)) { + async_data->async_res = ErrorTimeout; + } + } else { + http_context.stream_cv.wait(lock, condition); } - } else { - http_context.request_future.wait(); } - // Simulate small delay from HTTP receive. - return 1'000'000; + if (sd->load()) return -1LL; + return 1'000'000LL; }, [this, async_data](Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb(ctx, static_cast(ctx.CommandHeader().command_id.Value()), 1, @@ -700,26 +763,28 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { } Context& http_context = GetContext(async_data->context_handle); - const std::size_t remaining_data = + std::lock_guard lock(http_context.stream_mutex); + const bool download_complete = + http_context.request_future.wait_for(std::chrono::milliseconds(0)) == + std::future_status::ready; + const size_t available = http_context.response.body.size() - http_context.current_copied_data; + const size_t to_copy = + std::min(available, static_cast(async_data->buffer_size)); - if (async_data->buffer_size >= remaining_data) { + if (to_copy > 0) { async_data->buffer->Write(http_context.response.body.data() + http_context.current_copied_data, - 0, remaining_data); - http_context.current_copied_data += remaining_data; + 0, to_copy); + http_context.current_copied_data += to_copy; + } + + if (download_complete && to_copy == available) { http_context.state = RequestState::Completed; rb.Push(ResultSuccess); } else { - async_data->buffer->Write(http_context.response.body.data() + - http_context.current_copied_data, - 0, async_data->buffer_size); - http_context.current_copied_data += async_data->buffer_size; rb.Push(ErrorBufferSmall); } - LOG_DEBUG(Service_HTTP, "Receive: buffer_size= {}, total_copied={}, total_body={}", - async_data->buffer_size, http_context.current_copied_data, - http_context.response.body.size()); }); } @@ -836,14 +901,18 @@ void HTTP_C::CancelConnection(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 context_handle = rp.Pop(); - LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); - const auto* session_data = EnsureSessionInitialized(ctx, rp); if (!session_data) { return; } - [[maybe_unused]] Context& http_context = GetContext(context_handle); + Context& http_context = GetContext(context_handle); + { + std::lock_guard lock(http_context.cancel_mutex); + if (http_context.cancel_stop_fn) { + http_context.cancel_stop_fn(); + } + } IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); @@ -1445,8 +1514,10 @@ void HTTP_C::GetResponseDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { return; } + async_pending->fetch_add(1, std::memory_order_relaxed); ctx.RunAsync( - [this, async_data](Kernel::HLERequestContext& ctx) { + [this, async_data, sd = async_shutdown, ap = async_pending](Kernel::HLERequestContext& ctx) { + SCOPE_EXIT({ ap->fetch_sub(1, std::memory_order_release); }); Context& http_context = GetContext(async_data->context_handle); if (async_data->timeout) { @@ -1459,7 +1530,7 @@ void HTTP_C::GetResponseDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { http_context.request_future.wait(); } - return 0; + return sd->load() ? -1LL : 0LL; }, [this, async_data](Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb(ctx, 2, 0); @@ -1540,21 +1611,23 @@ void HTTP_C::GetResponseHeaderImpl(Kernel::HLERequestContext& ctx, bool timeout) return; } + async_pending->fetch_add(1, std::memory_order_relaxed); ctx.RunAsync( - [this, async_data](Kernel::HLERequestContext& ctx) { + [this, async_data, sd = async_shutdown, ap = async_pending](Kernel::HLERequestContext& ctx) { + SCOPE_EXIT({ ap->fetch_sub(1, std::memory_order_release); }); Context& http_context = GetContext(async_data->context_handle); if (async_data->timeout) { - const auto wait_res = http_context.request_future.wait_for( + const auto wait_res = http_context.response_headers_future.wait_for( std::chrono::nanoseconds(async_data->timeout_nanos)); if (wait_res == std::future_status::timeout) { async_data->async_res = ErrorTimeout; } } else { - http_context.request_future.wait(); + http_context.response_headers_future.wait(); } - return 0; + return sd->load() ? -1LL : 0LL; }, [this, async_data](Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb(ctx, static_cast(ctx.CommandHeader().command_id.Value()), 2, @@ -1631,30 +1704,29 @@ void HTTP_C::GetResponseStatusCodeImpl(Kernel::HLERequestContext& ctx, bool time if (timeout) { async_data->timeout_nanos = rp.Pop(); - LOG_INFO(Service_HTTP, "called, timeout={}", async_data->timeout_nanos); - } else { - LOG_INFO(Service_HTTP, "called"); } if (!PerformStateChecks(ctx, rp, async_data->context_handle)) { return; } + async_pending->fetch_add(1, std::memory_order_relaxed); ctx.RunAsync( - [this, async_data](Kernel::HLERequestContext& ctx) { + [this, async_data, sd = async_shutdown, ap = async_pending](Kernel::HLERequestContext& ctx) { + SCOPE_EXIT({ ap->fetch_sub(1, std::memory_order_release); }); Context& http_context = GetContext(async_data->context_handle); if (async_data->timeout) { - const auto wait_res = http_context.request_future.wait_for( + const auto wait_res = http_context.response_headers_future.wait_for( std::chrono::nanoseconds(async_data->timeout_nanos)); if (wait_res == std::future_status::timeout) { LOG_DEBUG(Service_HTTP, "Status code: {}", "timeout"); async_data->async_res = ErrorTimeout; } } else { - http_context.request_future.wait(); + http_context.response_headers_future.wait(); } - return 0; + return sd->load() ? -1LL : 0LL; }, [this, async_data](Kernel::HLERequestContext& ctx) { if (async_data->async_res != ResultSuccess) { @@ -1666,6 +1738,15 @@ void HTTP_C::GetResponseStatusCodeImpl(Kernel::HLERequestContext& ctx, bool time Context& http_context = GetContext(async_data->context_handle); + // If headers were never received (send failed before response_handler fired), + // status will still be -1; treat that as a connection error. + if (http_context.response.status < 0) { + IPC::RequestBuilder rb( + ctx, static_cast(ctx.CommandHeader().command_id.Value()), 1, 0); + rb.Push(ErrorStateError); + return; + } + const u32 response_code = http_context.response.status; LOG_DEBUG(Service_HTTP, "Status code: {}, response_code={}", "good", response_code); @@ -2007,8 +2088,6 @@ void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const Context::Handle context_handle = rp.Pop(); - LOG_INFO(Service_HTTP, "called"); - const auto* session_data = EnsureSessionInitialized(ctx, rp); if (!session_data) { return; @@ -2016,17 +2095,20 @@ void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) { Context& http_context = GetContext(context_handle); - // On the real console, the current downloaded progress and the total size of the content gets - // returned. Since we do not support chunked downloads on the host, always return the content - // length if the download is complete and 0 otherwise. + // Return Content-Length from response headers as soon as headers are available, matching + // real hardware behavior where totalSize is populated before body bytes arrive. u32 content_length = 0; - const bool is_complete = http_context.request_future.wait_for(std::chrono::milliseconds(0)) == - std::future_status::ready; - if (is_complete) { + const bool headers_ready = + http_context.response_headers_future.wait_for(std::chrono::milliseconds(0)) == + std::future_status::ready; + if (headers_ready) { const auto& headers = http_context.response.headers; - const auto& it = headers.find("Content-Length"); + const auto it = headers.find("Content-Length"); if (it != headers.end()) { - content_length = std::stoi(it->second); + try { + content_length = static_cast(std::stoul(it->second)); + } catch (const std::exception&) { + } } } @@ -2171,6 +2253,163 @@ void HTTP_C::DecryptClCertA() { ClCertA.init = true; } +void HTTP_C::CreateRootCertChain(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto* session_data = EnsureSessionInitialized(ctx, rp); + if (!session_data) { + return; + } + + auto chain = std::make_shared(); + chain->handle = ++root_cert_chains_counter; + chain->session_id = session_data->session_id; + root_cert_chains[chain->handle] = chain; + + LOG_DEBUG(Service_HTTP, "called, chain_handle={}", chain->handle); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(chain->handle); +} + +void HTTP_C::DestroyRootCertChain(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const RootCertChain::Handle chain_handle = rp.Pop(); + + LOG_DEBUG(Service_HTTP, "called, chain_handle={}", chain_handle); + + auto* session_data = EnsureSessionInitialized(ctx, rp); + if (!session_data) { + return; + } + + root_cert_chains.erase(chain_handle); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void HTTP_C::RootCertChainAddCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const RootCertChain::Handle chain_handle = rp.Pop(); + const u32 cert_size = rp.Pop(); + auto& cert_buffer = rp.PopMappedBuffer(); + + LOG_DEBUG(Service_HTTP, "called, chain_handle={}, cert_size={}", chain_handle, cert_size); + + auto* session_data = EnsureSessionInitialized(ctx, rp); + if (!session_data) { + return; + } + + auto it = root_cert_chains.find(chain_handle); + if (it == root_cert_chains.end()) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(ErrorStateError); + rb.Push(0); + rb.PushMappedBuffer(cert_buffer); + return; + } + + RootCertChain::RootCACert cert; + cert.handle = static_cast(it->second->certificates.size()) + 1; + cert.session_id = session_data->session_id; + cert.certificate.resize(cert_size); + cert_buffer.Read(cert.certificate.data(), 0, cert_size); + const u32 cert_handle = cert.handle; + it->second->certificates.push_back(std::move(cert)); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(ResultSuccess); + rb.Push(cert_handle); + rb.PushMappedBuffer(cert_buffer); +} + +void HTTP_C::RootCertChainAddDefaultCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const RootCertChain::Handle chain_handle = rp.Pop(); + const u32 cert_id = rp.Pop(); + + LOG_DEBUG(Service_HTTP, "called, chain_handle={}, cert_id={}", chain_handle, cert_id); + + auto* session_data = EnsureSessionInitialized(ctx, rp); + if (!session_data) { + return; + } + + auto it = root_cert_chains.find(chain_handle); + if (it == root_cert_chains.end()) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ErrorStateError); + rb.Push(0); + return; + } + + RootCertChain::RootCACert cert; + cert.handle = static_cast(it->second->certificates.size()) + 1; + cert.session_id = session_data->session_id; + const u32 cert_handle = cert.handle; + it->second->certificates.push_back(std::move(cert)); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(cert_handle); +} + +void HTTP_C::RootCertChainRemoveCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const RootCertChain::Handle chain_handle = rp.Pop(); + const u32 cert_handle = rp.Pop(); + + LOG_DEBUG(Service_HTTP, "called, chain_handle={}, cert_handle={}", chain_handle, cert_handle); + + auto* session_data = EnsureSessionInitialized(ctx, rp); + if (!session_data) { + return; + } + + auto it = root_cert_chains.find(chain_handle); + if (it != root_cert_chains.end()) { + auto& certs = it->second->certificates; + certs.erase(std::remove_if(certs.begin(), certs.end(), + [cert_handle](const RootCertChain::RootCACert& c) { + return c.handle == cert_handle; + }), + certs.end()); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void HTTP_C::SelectRootCertChain(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + const RootCertChain::Handle chain_handle = rp.Pop(); + + LOG_DEBUG(Service_HTTP, "called, context_handle={}, chain_handle={}", context_handle, + chain_handle); + + if (!PerformStateChecks(ctx, rp, context_handle)) { + return; + } + + Context& http_context = GetContext(context_handle); + + auto it = root_cert_chains.find(chain_handle); + if (it == root_cert_chains.end()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ErrorStateError); + return; + } + + http_context.ssl_config.root_ca_chain = it->second; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { static const FunctionInfo functions[] = { // clang-format off @@ -2211,18 +2450,18 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"}, {0x0024, &HTTP_C::AddTrustedRootCA, "AddTrustedRootCA"}, {0x0025, &HTTP_C::AddDefaultCert, "AddDefaultCert"}, - {0x0026, nullptr, "SelectRootCertChain"}, + {0x0026, &HTTP_C::SelectRootCertChain, "SelectRootCertChain"}, {0x0027, nullptr, "SetClientCert"}, {0x0028, &HTTP_C::SetDefaultClientCert, "SetDefaultClientCert"}, {0x0029, &HTTP_C::SetClientCertContext, "SetClientCertContext"}, {0x002A, &HTTP_C::GetSSLError, "GetSSLError"}, {0x002B, &HTTP_C::SetSSLOpt, "SetSSLOpt"}, {0x002C, nullptr, "SetSSLClearOpt"}, - {0x002D, nullptr, "CreateRootCertChain"}, - {0x002E, nullptr, "DestroyRootCertChain"}, - {0x002F, nullptr, "RootCertChainAddCert"}, - {0x0030, nullptr, "RootCertChainAddDefaultCert"}, - {0x0031, nullptr, "RootCertChainRemoveCert"}, + {0x002D, &HTTP_C::CreateRootCertChain, "CreateRootCertChain"}, + {0x002E, &HTTP_C::DestroyRootCertChain, "DestroyRootCertChain"}, + {0x002F, &HTTP_C::RootCertChainAddCert, "RootCertChainAddCert"}, + {0x0030, &HTTP_C::RootCertChainAddDefaultCert, "RootCertChainAddDefaultCert"}, + {0x0031, &HTTP_C::RootCertChainRemoveCert, "RootCertChainRemoveCert"}, {0x0032, &HTTP_C::OpenClientCertContext, "OpenClientCertContext"}, {0x0033, &HTTP_C::OpenDefaultClientCertContext, "OpenDefaultClientCertContext"}, {0x0034, &HTTP_C::CloseClientCertContext, "CloseClientCertContext"}, @@ -2238,6 +2477,19 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { DecryptClCertA(); } +HTTP_C::~HTTP_C() { + *async_shutdown = true; + for (auto& [handle, ctx] : contexts) { + std::lock_guard lock(ctx.cancel_mutex); + if (ctx.cancel_stop_fn) { + ctx.cancel_stop_fn(); + } + } + while (async_pending->load(std::memory_order_acquire) > 0) { + std::this_thread::yield(); + } +} + std::shared_ptr GetService(Core::System& system) { return system.ServiceManager().GetService("http:C"); } diff --git a/src/core/hle/service/http/http_c.h b/src/core/hle/service/http/http_c.h index 28f7c4795..81f8d738e 100644 --- a/src/core/hle/service/http/http_c.h +++ b/src/core/hle/service/http/http_c.h @@ -4,8 +4,10 @@ #pragma once +#include #include #include +#include #include #include #include @@ -286,6 +288,12 @@ public: u32 chunked_content_length; std::future request_future; + std::promise response_headers_promise; + std::future response_headers_future{response_headers_promise.get_future()}; + std::mutex cancel_mutex; + std::function cancel_stop_fn; + std::mutex stream_mutex; + std::condition_variable stream_cv; std::atomic current_download_size_bytes; std::atomic total_download_size_bytes; std::size_t current_copied_data; @@ -339,6 +347,7 @@ private: class HTTP_C final : public ServiceFramework { public: HTTP_C(); + ~HTTP_C(); const ClCertAData& GetClCertA() const { return ClCertA; @@ -836,6 +845,13 @@ private: */ void CloseClientCertContext(Kernel::HLERequestContext& ctx); + void CreateRootCertChain(Kernel::HLERequestContext& ctx); + void DestroyRootCertChain(Kernel::HLERequestContext& ctx); + void RootCertChainAddCert(Kernel::HLERequestContext& ctx); + void RootCertChainAddDefaultCert(Kernel::HLERequestContext& ctx); + void RootCertChainRemoveCert(Kernel::HLERequestContext& ctx); + void SelectRootCertChain(Kernel::HLERequestContext& ctx); + /** * HTTP_C::SetKeepAlive service function * Inputs: @@ -883,6 +899,9 @@ private: /// The next handle number to use when a new ClientCert context is created. ClientCertContext::Handle client_certs_counter = 0; + /// The next handle number to use when a new RootCertChain is created. + RootCertChain::Handle root_cert_chains_counter = 0; + /// Global list of HTTP contexts currently opened. std::unordered_map contexts; @@ -896,8 +915,15 @@ private: /// Global list of ClientCert contexts currently opened. std::unordered_map> client_certs; + /// Global list of RootCertChains currently opened. + std::unordered_map> root_cert_chains; + ClCertAData ClCertA; + // Shutdown synchronization: set flag and wait for in-flight RunAsync lambdas in destructor. + std::shared_ptr> async_shutdown{std::make_shared>(false)}; + std::shared_ptr> async_pending{std::make_shared>(0)}; + private: template void serialize(Archive& ar, const unsigned int) {