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 <noreply@anthropic.com>
This commit is contained in:
Masamune3210 2026-05-13 06:30:59 -05:00
parent 8d71f4afa3
commit e59960c518
4 changed files with 355 additions and 62 deletions

View file

@ -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

View file

@ -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<u8> buffer;
buffer.resize(0x10000);
auto file_size = in_file->GetSize();

View file

@ -2,7 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <atomic>
#include <thread>
#include <tuple>
#include <unordered_map>
#include <boost/algorithm/string/replace.hpp>
@ -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<std::mutex> 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<std::mutex> 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<u16>(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<std::mutex> 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<size_t>(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<u32>();
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<u16>(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<u64>();
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<u16>(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<u32>();
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<u32>(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<RootCertChain>();
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<u32>();
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<u32>();
const u32 cert_size = rp.Pop<u32>();
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<u32>(0);
rb.PushMappedBuffer(cert_buffer);
return;
}
RootCertChain::RootCACert cert;
cert.handle = static_cast<u32>(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<u32>();
const u32 cert_id = rp.Pop<u32>();
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<u32>(0);
return;
}
RootCertChain::RootCACert cert;
cert.handle = static_cast<u32>(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<u32>();
const u32 cert_handle = rp.Pop<u32>();
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<u32>();
const RootCertChain::Handle chain_handle = rp.Pop<u32>();
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<HTTP_C> GetService(Core::System& system) {
return system.ServiceManager().GetService<HTTP_C>("http:C");
}

View file

@ -4,8 +4,10 @@
#pragma once
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
@ -286,6 +288,12 @@ public:
u32 chunked_content_length;
std::future<void> request_future;
std::promise<void> response_headers_promise;
std::future<void> response_headers_future{response_headers_promise.get_future()};
std::mutex cancel_mutex;
std::function<void()> cancel_stop_fn;
std::mutex stream_mutex;
std::condition_variable stream_cv;
std::atomic<u64> current_download_size_bytes;
std::atomic<u64> total_download_size_bytes;
std::size_t current_copied_data;
@ -339,6 +347,7 @@ private:
class HTTP_C final : public ServiceFramework<HTTP_C, SessionData> {
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<Context::Handle, Context> contexts;
@ -896,8 +915,15 @@ private:
/// Global list of ClientCert contexts currently opened.
std::unordered_map<ClientCertContext::Handle, std::shared_ptr<ClientCertContext>> client_certs;
/// Global list of RootCertChains currently opened.
std::unordered_map<RootCertChain::Handle, std::shared_ptr<RootCertChain>> root_cert_chains;
ClCertAData ClCertA;
// Shutdown synchronization: set flag and wait for in-flight RunAsync lambdas in destructor.
std::shared_ptr<std::atomic<bool>> async_shutdown{std::make_shared<std::atomic<bool>>(false)};
std::shared_ptr<std::atomic<int>> async_pending{std::make_shared<std::atomic<int>>(0)};
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {