diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4406c3d660..4d79bd78b9 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -198,7 +198,7 @@ if (ENABLE_WEB_SERVICE) # httplib add_library(httplib INTERFACE) target_include_directories(httplib INTERFACE ./httplib) - target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) + target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT -DCPPHTTPLIB_NO_DEFAULT_USER_AGENT) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) # cpp-jwt diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h index 3657b47946..e75bcc75f4 100644 --- a/externals/httplib/httplib.h +++ b/externals/httplib/httplib.h @@ -3543,7 +3543,16 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, inline ssize_t write_headers(Stream &strm, const Headers &headers) { ssize_t write_len = 0; + auto it = headers.find("Host"); + if (it != headers.end()) { + auto len = + strm.write_format("%s: %s\r\n", it->first.c_str(), it->second.c_str()); + if (len < 0) { return len; } + write_len += len; + } for (const auto &x : headers) { + if (x.first == "Host") + continue; auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); if (len < 0) { return len; } @@ -6286,7 +6295,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, // Prepare additional headers if (close_connection) { if (!req.has_header("Connection")) { - req.headers.emplace("Connection", "close"); + //req.headers.emplace("Connection", "close"); } } @@ -6306,7 +6315,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, } } - if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + //if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } #ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT if (!req.has_header("User-Agent")) { @@ -6331,7 +6340,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, } } else { if (!req.has_header("Content-Type")) { - req.headers.emplace("Content-Type", "text/plain"); + //req.headers.emplace("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 214241f2b2..28bcd43295 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -76,6 +76,31 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_ } } +void Timing::ScheduleEventTS(s64 cycles_into_future, const TimingEventType* event_type, + std::uintptr_t user_data, std::size_t core_id) { + if (event_queue_locked) { + return; + } + + ASSERT(event_type != nullptr); + Timing::Timer* timer = nullptr; + if (core_id == std::numeric_limits::max()) { + timer = current_timer; + } else { + ASSERT(core_id < timers.size()); + timer = timers.at(core_id).get(); + } + + // Events scheduled with this thread safe version come after blocking operations with + // unpredictable timings in the host machine, so there is no need to be cycle accurate. + // To prevent the event from scheduling before the next advance(), we set a minimum time + // of MAX_SLICE_LENGTH * 2 cycles into the future. + cycles_into_future = std::max(static_cast(MAX_SLICE_LENGTH * 2), cycles_into_future); + + timer->ts_queue.Push( + Event{static_cast(timer->GetTicks() + cycles_into_future), 0, user_data, event_type}); +} + void Timing::UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data) { if (event_queue_locked) { return; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 998c3886d1..6b9b9cb05c 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -258,6 +258,10 @@ public: std::uintptr_t user_data = 0, std::size_t core_id = std::numeric_limits::max()); + void ScheduleEventTS(s64 cycles_into_future, const TimingEventType* event_type, + std::uintptr_t user_data = 0, + std::size_t core_id = std::numeric_limits::max()); + void UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data); /// We only permit one event of each type in the queue at a time. diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 3c287946a7..48a4a4d3e1 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -247,6 +248,76 @@ public: std::chrono::nanoseconds timeout, std::shared_ptr callback); +private: + template + class AsyncWakeUpCallback : public WakeupCallback { + public: + explicit AsyncWakeUpCallback(ResultFunctor res_functor, std::future fut) + : functor(res_functor) { + future = std::move(fut); + } + + void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + functor(ctx); + } + + private: + ResultFunctor functor; + std::future future; + + template + void serialize(Archive& ar, const unsigned int) { + if (!Archive::is_loading::value && future.valid()) { + future.wait(); + } + ar& functor; + } + friend class boost::serialization::access; + }; + +public: + /** + * Puts the game thread to sleep and calls the specified async_section from another thread. + * Once the execution of the async section finishes, result_function is called. Use this + * mechanism to run blocking IO operations, so that other game threads are allowed to run + * while the one performing the blocking operation waits. + * @param async_section Callable that takes Kernel::HLERequestContext& as argument + * and returns the amount of nanoseconds to wait before calling result_function. + * This callable is ran from a different thread. + * @param result_function Callable that takes Kernel::HLERequestContext& as argument + * and doesn't return anything. This callable is ran from the emulator thread + * and can be used to set the IPC result. + * @param really_async If set to false, it will call async_section and result_function + * from the emulator thread without resorting to new threads. + */ + template + void RunAsync(AsyncFunctor async_section, ResultFunctor result_function, + bool really_async = true) { + + if (really_async) { + this->SleepClientThread( + "RunAsync", std::chrono::nanoseconds(-1), + std::make_shared>( + result_function, + std::move(std::async(std::launch::async, [this, async_section] { + s64 sleepfor = async_section(*this); + this->thread->WakeAfterDelayTS(sleepfor); + })))); + + } else { + s64 sleepfor = async_section(*this); + if (sleepfor > 0) { + auto parallel_wakeup = std::make_shared>( + result_function, std::move(std::future())); + this->SleepClientThread("RunInPool", std::chrono::nanoseconds(sleepfor), + parallel_wakeup); + } else { + result_function(*this); + } + } + } + /** * Resolves a object id from the request command buffer into a pointer to an object. See the * "HLE handle protocol" section in the class documentation for more details. diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5c2c44b4ad..98d6e27552 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -262,6 +262,15 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { thread_manager.ThreadWakeupEventType, thread_id); } +void Thread::WakeAfterDelayTS(s64 nanoseconds) { + // Don't schedule a wakeup if the thread wants to wait forever + if (nanoseconds == -1) + return; + + thread_manager.kernel.timing.ScheduleEventTS( + nsToCycles(nanoseconds), thread_manager.ThreadWakeupEventType, thread_id, core_id); +} + void Thread::ResumeFromWait() { ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 543b8fa3c1..5b92b798a0 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -241,6 +241,12 @@ public: */ void WakeAfterDelay(s64 nanoseconds); + /** + * Schedules an event to wake up the specified thread after the specified delay, thread safe + * @param nanoseconds The time this thread will be allowed to sleep for + */ + void WakeAfterDelayTS(s64 nanoseconds); + /** * Sets the result after the thread awakens (from either WaitSynchronization SVC) * @param result Value to set to the returned result diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h index ea89e1095c..6cabc05339 100644 --- a/src/core/hle/service/frd/frd.h +++ b/src/core/hle/service/frd/frd.h @@ -152,10 +152,10 @@ private: struct ServiceLocatorData { s32_le result{}; s32_le http_status_code{}; + std::array service_host{}; std::array service_token{}; u8 status; - std::array service_host{}; - std::array padding; + std::array padding{}; u64_le server_time{}; void Init() { @@ -170,7 +170,6 @@ private: ar& service_token; ar& status; ar& service_host; - ar& padding; ar& server_time; } friend class boost::serialization::access; diff --git a/src/core/hle/service/http/http_c.cpp b/src/core/hle/service/http/http_c.cpp index 1058640b52..c0db50f5c8 100644 --- a/src/core/hle/service/http/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include @@ -65,10 +66,10 @@ static std::pair SplitUrl(const std::string& url) { std::string path; if (path_index == std::string::npos) { // If no path is specified after the host, set it to "/" - host = url; + host = url.substr(prefix_end); path = "/"; } else { - host = url.substr(0, path_index); + host = url.substr(prefix_end, path_index - prefix_end); path = url.substr(path_index); } return std::make_pair(host, path); @@ -77,38 +78,20 @@ static std::pair SplitUrl(const std::string& url) { void Context::MakeRequest() { ASSERT(state == RequestState::NotStarted); -#ifdef ENABLE_WEB_SERVICE - const auto& [host, path] = SplitUrl(url.c_str()); - std::unique_ptr client = std::make_unique(host); - SSL_CTX* ctx = client->ssl_context(); - if (ctx) { - if (auto client_cert = ssl_config.client_cert_ctx.lock()) { - SSL_CTX_use_certificate_ASN1(ctx, static_cast(client_cert->certificate.size()), - client_cert->certificate.data()); - SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, client_cert->private_key.data(), - static_cast(client_cert->private_key.size())); - } - - // TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly - // https://www.3dbrew.org/wiki/SSL_Services#SSLOpt - // Hack: Since for now RootCerts are not implemented we set the VerifyMode to None. - SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); - } - - state = RequestState::InProgress; - static const std::unordered_map request_method_strings{ {RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"}, {RequestMethod::Head, "HEAD"}, {RequestMethod::Put, "PUT"}, {RequestMethod::Delete, "DELETE"}, {RequestMethod::PostEmpty, "POST"}, {RequestMethod::PutEmpty, "PUT"}, }; + + const auto& [host, path] = SplitUrl(url.c_str()); httplib::Request request; - httplib::Error error; + httplib::Error error{-1}; request.method = request_method_strings.at(method); request.path = path; - // TODO(B3N30): Add post data body + 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; @@ -120,14 +103,71 @@ void Context::MakeRequest() { request.headers.emplace(header.name, header.value); } - if (!client->send(request, response, error)) { - LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); - state = RequestState::TimedOut; - } else { - LOG_DEBUG(Service_HTTP, "Request successful"); - // TODO(B3N30): Verify this state on HW - state = RequestState::ReadyToDownloadContent; + if (!post_data.empty()) { + request.headers.emplace("Content-Type", "application/x-www-form-urlencoded"); + request.body = httplib::detail::params_to_query_str(post_data); + boost::replace_all(request.body, "*", "%2A"); } + + if (!post_data_raw.empty()) { + request.body = post_data_raw; + } + + state = RequestState::InProgress; + + const unsigned char* tmpCertPtr = clcert_data.certificate.data(); + const unsigned char* tmpKeyPtr = clcert_data.private_key.data(); + X509* cert = d2i_X509(nullptr, &tmpCertPtr, (long)clcert_data.certificate.size()); + EVP_PKEY* key = + d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &tmpKeyPtr, (long)clcert_data.private_key.size()); + // Sadly, we have to duplicate code, the class hierarchy in httplib is not very useful... + if (uses_default_client_cert) { + + std::unique_ptr client = + std::make_unique(host, 443, cert, key); + + // TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly + // https://www.3dbrew.org/wiki/SSL_Services#SSLOpt + // Hack: Since for now RootCerts are not implemented we set the VerifyMode to None. + client->enable_server_certificate_verification(false); + + if (!client->send(request, response, error)) { + LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); + state = RequestState::TimedOut; + } else { + LOG_DEBUG(Service_HTTP, "Request successful"); + // TODO(B3N30): Verify this state on HW + state = RequestState::ReadyToDownloadContent; + } + } else { + std::unique_ptr client = std::make_unique(host); + SSL_CTX* ctx = client->ssl_context(); + if (ctx) { + if (auto client_cert = ssl_config.client_cert_ctx.lock()) { + SSL_CTX_use_certificate_ASN1(ctx, static_cast(client_cert->certificate.size()), + client_cert->certificate.data()); + SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, client_cert->private_key.data(), + static_cast(client_cert->private_key.size())); + } + + // TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly + // https://www.3dbrew.org/wiki/SSL_Services#SSLOpt + // Hack: Since for now RootCerts are not implemented we set the VerifyMode to None. + client->enable_server_certificate_verification(false); + } + + if (!client->send(request, response, error)) { + LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); + state = RequestState::TimedOut; + } else { + LOG_DEBUG(Service_HTTP, "Request successful"); + // TODO(B3N30): Verify this state on HW + state = RequestState::ReadyToDownloadContent; + } + } + +#ifdef ENABLE_WEB_SERVICE + #else LOG_ERROR(Service_HTTP, "Tried to make request but WebServices is not enabled in this build"); state = RequestState::TimedOut; @@ -221,6 +261,7 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) { itr->second.request_future = std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second)); + itr->second.current_copied_data = 0; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -248,6 +289,7 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) { itr->second.request_future = std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second)); + itr->second.current_copied_data = 0; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -264,7 +306,7 @@ void HTTP_C::ReceiveDataTimeout(Kernel::HLERequestContext& ctx) { void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { IPC::RequestParser rp(ctx); const Context::Handle context_handle = rp.Pop(); - [[maybe_unused]] const u32 buffer_size = rp.Pop(); + u32 buffer_size = rp.Pop(); u64 timeout_nanos = 0; if (timeout) { timeout_nanos = rp.Pop(); @@ -273,6 +315,8 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { LOG_WARNING(Service_HTTP, "(STUBBED) called"); } + Kernel::MappedBuffer& buffer = rp.PopMappedBuffer(); + if (!PerformStateChecks(ctx, rp, context_handle)) { return; } @@ -280,12 +324,46 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { auto itr = contexts.find(context_handle); ASSERT(itr != contexts.end()); + Context& http_context = itr->second; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (timeout) { - itr->second.request_future.wait_for(std::chrono::nanoseconds(timeout_nanos)); + auto wait_res = http_context.request_future.wait_for( + std::chrono::nanoseconds(timeout_nanos)); + if (wait_res == std::future_status::timeout) { + rb.Push(ResultCode(105, ErrorModule::HTTP, ErrorSummary::NothingHappened, + ErrorLevel::Permanent)); + return; + } } else { - itr->second.request_future.wait(); + http_context.request_future.wait(); } + size_t remaining_data = http_context.response.body.size() - http_context.current_copied_data; + + if (buffer_size >= remaining_data) { + buffer.Write(http_context.response.body.data() + http_context.current_copied_data, 0, + remaining_data); + http_context.current_copied_data += remaining_data; + rb.Push(RESULT_SUCCESS); + } + else { + buffer.Write(http_context.response.body.data() + http_context.current_copied_data, 0, + buffer_size); + http_context.current_copied_data += buffer_size; + rb.Push(ResultCode(43, ErrorModule::HTTP, ErrorSummary::WouldBlock, ErrorLevel::Permanent)); + } + LOG_DEBUG(Service_HTTP, "Receive: buffer_size= {}, total_copied={}, total_body={}", buffer_size, + http_context.current_copied_data, http_context.response.body.size()); + ctx.SleepClientThread("http_data", std::chrono::nanoseconds(100000), nullptr); +} + +void HTTP_C::SetProxyDefault(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } @@ -392,6 +470,24 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +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; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 context_handle = rp.Pop(); @@ -474,17 +570,82 @@ void HTTP_C::AddPostDataAscii(Kernel::HLERequestContext& ctx) { return; } - ASSERT(std::find_if(itr->second.post_data.begin(), itr->second.post_data.end(), - [&name](const Context::PostData& m) -> bool { return m.name == name; }) == - itr->second.post_data.end()); - - itr->second.post_data.emplace_back(name, value); + itr->second.post_data.emplace(name, value); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(value_buffer); } +void HTTP_C::AddPostDataRaw(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u32 context_handle = rp.Pop(); + const u32 post_data_len = rp.Pop(); + auto buffer = rp.PopMappedBuffer(); + + LOG_DEBUG(Service_HTTP, "context_handle={}, post_data_len={}", context_handle, + post_data_len); + + if (!PerformStateChecks(ctx, rp, context_handle)) { + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.post_data_raw.resize(buffer.GetSize()); + buffer.Read(itr->second.post_data_raw.data(), 0, buffer.GetSize()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(buffer); +} + +void HTTP_C::GetResponseHeader(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u32 context_handle = rp.Pop(); + const u32 name_len = rp.Pop(); + [[maybe_unused]] const u32 value_max_len = rp.Pop(); + auto& header_name = rp.PopStaticBuffer(); + Kernel::MappedBuffer& value_buffer = rp.PopMappedBuffer(); + std::string header_name_str(reinterpret_cast(header_name.data()), name_len); + while (header_name_str.size() && header_name_str.back() == '\0') { + header_name_str.pop_back(); + } + + if (!PerformStateChecks(ctx, rp, context_handle)) { + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.request_future.wait(); + + auto& headers = itr->second.response.headers; + u32 copied_size = 0; + + LOG_DEBUG(Service_HTTP, "header={}, max_len={}", header_name_str, value_buffer.GetSize()); + + auto header = headers.find(header_name_str); + if (header != headers.end()) { + std::string header_value = header->second; + copied_size = (u32)header_value.size(); + if (header_value.size() + 1 > value_buffer.GetSize()) { + header_value.resize(value_buffer.GetSize() - 1); + } + header_value.push_back('\0'); + value_buffer.Write(header_value.data(), 0, header_value.size()); + } else { + LOG_WARNING(Service_HTTP, "header={} not found", header_name_str); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push(copied_size); + rb.PushMappedBuffer(value_buffer); +} + void HTTP_C::GetResponseStatusCode(Kernel::HLERequestContext& ctx) { GetResponseStatusCodeImpl(ctx, false); } @@ -493,6 +654,31 @@ void HTTP_C::GetResponseStatusCodeTimeout(Kernel::HLERequestContext& ctx) { GetResponseStatusCodeImpl(ctx, true); } +void HTTP_C::AddTrustedRootCA(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + [[maybe_unused]] const u32 root_ca_len = rp.Pop(); + auto root_ca_data = rp.PopMappedBuffer(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(root_ca_data); +} + +void HTTP_C::AddDefaultCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + const u32 certificate_id = rp.Pop(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}, certificate_id={}", context_handle, + certificate_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + void HTTP_C::GetResponseStatusCodeImpl(Kernel::HLERequestContext& ctx, bool timeout) { IPC::RequestParser rp(ctx); const Context::Handle context_handle = rp.Pop(); @@ -512,18 +698,49 @@ void HTTP_C::GetResponseStatusCodeImpl(Kernel::HLERequestContext& ctx, bool time ASSERT(itr != contexts.end()); if (timeout) { - itr->second.request_future.wait_for(std::chrono::nanoseconds(timeout)); + auto wait_res = itr->second.request_future.wait_for(std::chrono::nanoseconds(timeout)); + if (wait_res == std::future_status::timeout) { + LOG_DEBUG(Service_HTTP, "Status code: {}", "timeout"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(105, ErrorModule::HTTP, ErrorSummary::NothingHappened, + ErrorLevel::Permanent)); + return; + } } else { itr->second.request_future.wait(); } const u32 response_code = itr->second.response.status; + LOG_DEBUG(Service_HTTP, "Status code: {}, response_code={}", "good", response_code); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(response_code); } +void HTTP_C::SetDefaultClientCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + // TODO(PabloMK7): There is only a single cert ID with value 64. Check it is valid and return error if not. + [[maybe_unused]] const u32 client_cert_id = rp.Pop(); + + + LOG_DEBUG(Service_HTTP, "client_cert_id={}", client_cert_id); + + if (!PerformStateChecks(ctx, rp, context_handle)) { + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.uses_default_client_cert = true; + itr->second.clcert_data = GetClCertA(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + void HTTP_C::SetClientCertContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 context_handle = rp.Pop(); @@ -764,10 +981,11 @@ void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) { content_length = std::stoi(it->second); } } - + LOG_DEBUG(Service_HTTP, "current={}, total={}", + itr->second.current_copied_data, content_length); IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); rb.Push(RESULT_SUCCESS); - rb.Push(content_length); + rb.Push(itr->second.current_copied_data); rb.Push(content_length); } @@ -897,7 +1115,7 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x0001, &HTTP_C::Initialize, "Initialize"}, {0x0002, &HTTP_C::CreateContext, "CreateContext"}, {0x0003, &HTTP_C::CloseContext, "CloseContext"}, - {0x0004, nullptr, "CancelConnection"}, + {0x0004, &HTTP_C::CancelConnection, "CancelConnection"}, {0x0005, nullptr, "GetRequestState"}, {0x0006, &HTTP_C::GetDownloadSizeState, "GetDownloadSizeState"}, {0x0007, nullptr, "GetRequestError"}, @@ -907,13 +1125,13 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x000B, &HTTP_C::ReceiveData, "ReceiveData"}, {0x000C, &HTTP_C::ReceiveDataTimeout, "ReceiveDataTimeout"}, {0x000D, nullptr, "SetProxy"}, - {0x000E, nullptr, "SetProxyDefault"}, + {0x000E, &HTTP_C::SetProxyDefault, "SetProxyDefault"}, {0x000F, nullptr, "SetBasicAuthorization"}, {0x0010, nullptr, "SetSocketBufferSize"}, {0x0011, &HTTP_C::AddRequestHeader, "AddRequestHeader"}, {0x0012, &HTTP_C::AddPostDataAscii, "AddPostDataAscii"}, {0x0013, nullptr, "AddPostDataBinary"}, - {0x0014, nullptr, "AddPostDataRaw"}, + {0x0014, &HTTP_C::AddPostDataRaw, "AddPostDataRaw"}, {0x0015, nullptr, "SetPostDataType"}, {0x0016, nullptr, "SendPostDataAscii"}, {0x0017, nullptr, "SendPostDataAsciiTimeout"}, @@ -923,16 +1141,17 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x001B, nullptr, "SendPOSTDataRawTimeout"}, {0x001C, nullptr, "SetPostDataEncoding"}, {0x001D, nullptr, "NotifyFinishSendPostData"}, - {0x001E, nullptr, "GetResponseHeader"}, + {0x001E, &HTTP_C::GetResponseHeader, "GetResponseHeader"}, {0x001F, nullptr, "GetResponseHeaderTimeout"}, {0x0020, nullptr, "GetResponseData"}, {0x0021, nullptr, "GetResponseDataTimeout"}, {0x0022, &HTTP_C::GetResponseStatusCode, "GetResponseStatusCode"}, {0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"}, - {0x0024, nullptr, "AddTrustedRootCA"}, - {0x0025, nullptr, "AddDefaultCert"}, + {0x0024, &HTTP_C::AddTrustedRootCA, "AddTrustedRootCA"}, + {0x0025, &HTTP_C::AddDefaultCert, "AddDefaultCert"}, {0x0026, nullptr, "SelectRootCertChain"}, {0x0027, nullptr, "SetClientCert"}, + {0x0028, &HTTP_C::SetDefaultClientCert, "SetDefaultClientCert"}, {0x0029, &HTTP_C::SetClientCertContext, "SetClientCertContext"}, {0x002A, &HTTP_C::GetSSLError, "GetSSLError"}, {0x002B, nullptr, "SetSSLOpt"}, diff --git a/src/core/hle/service/http/http_c.h b/src/core/hle/service/http/http_c.h index 615beae30e..d78e624e12 100644 --- a/src/core/hle/service/http/http_c.h +++ b/src/core/hle/service/http/http_c.h @@ -23,6 +23,7 @@ #endif #include #endif +#include "core/hle/ipc_helpers.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/service.h" @@ -112,6 +113,12 @@ private: friend class boost::serialization::access; }; +struct ClCertAData { + std::vector certificate; + std::vector private_key; + bool init = false; +}; + /// Represents an HTTP context. class Context final { public: @@ -167,22 +174,6 @@ public: friend class boost::serialization::access; }; - struct PostData { - // TODO(Subv): Support Binary and Raw POST elements. - PostData(std::string name, std::string value) : name(name), value(value){}; - PostData() = default; - std::string name; - std::string value; - - private: - template - void serialize(Archive& ar, const unsigned int) { - ar& name; - ar& value; - } - friend class boost::serialization::access; - }; - struct SSLConfig { u32 options; std::weak_ptr client_cert_ctx; @@ -198,6 +189,7 @@ public: friend class boost::serialization::access; }; + ClCertAData clcert_data; Handle handle; u32 session_id; std::string url; @@ -208,11 +200,14 @@ public: SSLConfig ssl_config{}; u32 socket_buffer_size; std::vector headers; - std::vector post_data; + httplib::Params post_data; + std::string post_data_raw; std::future request_future; std::atomic current_download_size_bytes; std::atomic total_download_size_bytes; + size_t current_copied_data; + bool uses_default_client_cert{}; #ifdef ENABLE_WEB_SERVICE httplib::Response response; #endif @@ -252,12 +247,6 @@ class HTTP_C final : public ServiceFramework { public: HTTP_C(); - struct ClCertAData { - std::vector certificate; - std::vector private_key; - bool init = false; - }; - const ClCertAData& GetClCertA() const { return ClCertA; } @@ -298,6 +287,8 @@ private: */ void CloseContext(Kernel::HLERequestContext& ctx); + void CancelConnection(Kernel::HLERequestContext& ctx); + /** * HTTP_C::GetDownloadSizeState service function * Inputs: @@ -369,6 +360,8 @@ private: */ void ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout); + void SetProxyDefault(Kernel::HLERequestContext& ctx); + /** * HTTP_C::AddRequestHeader service function * Inputs: @@ -399,6 +392,10 @@ private: */ void AddPostDataAscii(Kernel::HLERequestContext& ctx); + void AddPostDataRaw(Kernel::HLERequestContext& ctx); + + void GetResponseHeader(Kernel::HLERequestContext& ctx); + /** * HTTP_C::GetResponseStatusCode service function * Inputs: @@ -420,12 +417,26 @@ private: */ void GetResponseStatusCodeTimeout(Kernel::HLERequestContext& ctx); + void AddTrustedRootCA(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::AddDefaultCert service function + * Inputs: + * 1 : Context handle + * 2 : Cert ID + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void AddDefaultCert(Kernel::HLERequestContext& ctx); + /** * GetResponseStatusCodeImpl: * Implements GetResponseStatusCode and GetResponseStatusCodeTimeout service functions */ void GetResponseStatusCodeImpl(Kernel::HLERequestContext& ctx, bool timeout); + void SetDefaultClientCert(Kernel::HLERequestContext& ctx); + /** * HTTP_C::SetClientCertContext service function * Inputs: diff --git a/src/core/hle/service/soc/soc_u.cpp b/src/core/hle/service/soc/soc_u.cpp index 1b91fdaae9..21bca77505 100644 --- a/src/core/hle/service/soc/soc_u.cpp +++ b/src/core/hle/service/soc/soc_u.cpp @@ -301,7 +301,7 @@ std::pair SOC_U::TranslateSockOpt(int level, int opt) { return std::make_pair(SOL_SOCKET, opt); } -static void TranslateSockOptDataToPlatform(std::vector& out, const std::vector& in, +void SOC_U::TranslateSockOptDataToPlatform(std::vector& out, const std::vector& in, int platform_level, int platform_opt) { // linger structure may be different between 3DS and platform if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER && @@ -331,6 +331,10 @@ static void TranslateSockOptDataToPlatform(std::vector& out, const std::vect out = in; return; } + // Setting TTL to 0 means resetting it to the default value. + if (platform_level == IPPROTO_IP && platform_opt == IP_TTL && value == 0) { + value = SOC_TTL_DEFAULT; + } out.resize(sizeof(int)); std::memcpy(out.data(), &value, sizeof(int)); } @@ -1085,57 +1089,89 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) { #endif // _WIN32 u32 addr_len = rp.Pop(); rp.PopPID(); - auto& buffer = rp.PopMappedBuffer(); - CTRSockAddr ctr_src_addr; - std::vector output_buff(len); - std::vector addr_buff(addr_len); - sockaddr src_addr; - socklen_t src_addr_len = sizeof(src_addr); - - s32 ret = -1; - if (GetSocketBlocking(fd_info->second) && !dont_wait) { - PreTimerAdjust(); - } - - if (addr_len > 0) { - ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast(output_buff.data()), - len, flags, &src_addr, &src_addr_len); - if (ret >= 0 && src_addr_len > 0) { - ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); - std::memcpy(addr_buff.data(), &ctr_src_addr, addr_len); - } - } else { - ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast(output_buff.data()), - len, flags, NULL, 0); - addr_buff.resize(0); - } - int recv_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; - if (GetSocketBlocking(fd_info->second) && !dont_wait) { - PostTimerAdjust(ctx, "RecvFromOther"); - } + bool needs_parallel = GetSocketBlocking(fd_info->second) && !dont_wait; + struct ParallelData { + SOC_U* own{}; + Kernel::MappedBuffer buffer; + s32 ret{}; + u32 len{}; + u32 flags{}; + u32 addr_len{}; + std::vector output_buff; + std::vector addr_buff; + SocketHolder& fd_info; #ifdef _WIN32 - if (dont_wait && was_blocking) { - SetSocketBlocking(fd_info->second, true); - } + bool dont_wait; #endif - if (ret == SOCKET_ERROR_VALUE) { - ret = TranslateError(recv_error); - } else { - buffer.Write(output_buff.data(), 0, ret); - } + bool was_blocking; + int recv_error; + ParallelData(const Kernel::MappedBuffer& buf, SocketHolder& fd) + : buffer(buf), fd_info(fd) {} + }; - IPC::RequestBuilder rb = rp.MakeBuilder(2, 4); - rb.Push(RESULT_SUCCESS); - rb.Push(ret); - rb.PushStaticBuffer(std::move(addr_buff), 0); - rb.PushMappedBuffer(buffer); + auto parallel_data = std::make_shared(rp.PopMappedBuffer(), fd_info->second); + parallel_data->own = this; + parallel_data->ret = -1; + parallel_data->len = len; + parallel_data->flags = flags; + parallel_data->addr_len = addr_len; + parallel_data->output_buff.resize(len); + parallel_data->addr_buff.resize(addr_len); + parallel_data->was_blocking = was_blocking; +#ifdef _WIN32 + parallel_data->dont_wait = dont_wait; +#endif + + ctx.RunAsync( + [parallel_data](Kernel::HLERequestContext& ctx) { + sockaddr src_addr; + socklen_t src_addr_len = sizeof(src_addr); + CTRSockAddr ctr_src_addr; + if (parallel_data->addr_len > 0) { + parallel_data->ret = + ::recvfrom(parallel_data->fd_info.socket_fd, + reinterpret_cast(parallel_data->output_buff.data()), + parallel_data->len, parallel_data->flags, &src_addr, &src_addr_len); + if (parallel_data->ret >= 0 && src_addr_len > 0) { + ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); + std::memcpy(parallel_data->addr_buff.data(), &ctr_src_addr, + parallel_data->addr_len); + } + } else { + parallel_data->ret = + ::recvfrom(parallel_data->fd_info.socket_fd, + reinterpret_cast(parallel_data->output_buff.data()), + parallel_data->len, parallel_data->flags, NULL, 0); + parallel_data->addr_buff.resize(0); + } + parallel_data->recv_error = (parallel_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; + return 0; + }, + [parallel_data](Kernel::HLERequestContext& ctx) { + if (parallel_data->ret == SOCKET_ERROR_VALUE) { + parallel_data->ret = TranslateError(parallel_data->recv_error); + } else { + parallel_data->buffer.Write(parallel_data->output_buff.data(), 0, + parallel_data->ret); + } + +#ifdef _WIN32 + if (parallel_data->dont_wait && parallel_data->was_blocking) { + parallel_data->own->SetSocketBlocking(parallel_data->fd_info, true); + } +#endif + + IPC::RequestBuilder rb(ctx, 0x07, 2, 4); + rb.Push(RESULT_SUCCESS); + rb.Push(parallel_data->ret); + rb.PushStaticBuffer(std::move(parallel_data->addr_buff), 0); + rb.PushMappedBuffer(parallel_data->buffer); + }, + needs_parallel); } void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) { - // TODO(Subv): Calling this function on a blocking socket will block the emu thread, - // preventing graceful shutdown when closing the emulator, this can be fixed by always - // performing nonblocking operations and spinlock until the data is available IPC::RequestParser rp(ctx); u32 socket_handle = rp.Pop(); auto fd_info = open_sockets.find(socket_handle); @@ -1162,53 +1198,86 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) { u32 addr_len = rp.Pop(); rp.PopPID(); - CTRSockAddr ctr_src_addr; - std::vector output_buff(len); - std::vector addr_buff(addr_len); - sockaddr src_addr; - socklen_t src_addr_len = sizeof(src_addr); - - s32 ret = -1; - if (GetSocketBlocking(fd_info->second) && !dont_wait) { - PreTimerAdjust(); - } - if (addr_len > 0) { - // Only get src adr if input adr available - ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast(output_buff.data()), - len, flags, &src_addr, &src_addr_len); - if (ret >= 0 && src_addr_len > 0) { - ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); - std::memcpy(addr_buff.data(), &ctr_src_addr, addr_len); - } - } else { - ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast(output_buff.data()), - len, flags, NULL, 0); - addr_buff.resize(0); - } - int recv_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; - if (GetSocketBlocking(fd_info->second) && !dont_wait) { - PostTimerAdjust(ctx, "RecvFrom"); - } + bool needs_parallel = GetSocketBlocking(fd_info->second) && !dont_wait; + struct ParallelData { + SOC_U* own; + s32 ret{}; + u32 len{}; + u32 flags{}; + u32 addr_len{}; + std::vector output_buff; + std::vector addr_buff; + SocketHolder& fd_info; #ifdef _WIN32 - if (dont_wait && was_blocking) { - SetSocketBlocking(fd_info->second, true); - } + bool dont_wait; #endif - s32 total_received = ret; - if (ret == SOCKET_ERROR_VALUE) { - ret = TranslateError(recv_error); - total_received = 0; - } + bool was_blocking; + int recv_error; + ParallelData(SocketHolder& fd) : fd_info(fd) {} + }; - // Write only the data we received to avoid overwriting parts of the buffer with zeros - output_buff.resize(total_received); + auto parallel_data = std::make_shared(fd_info->second); + parallel_data->own = this; + parallel_data->ret = -1; + parallel_data->len = len; + parallel_data->flags = flags; + parallel_data->addr_len = addr_len; + parallel_data->output_buff.resize(len); + parallel_data->addr_buff.resize(addr_len); + parallel_data->was_blocking = was_blocking; +#ifdef _WIN32 + parallel_data->dont_wait = dont_wait; +#endif - IPC::RequestBuilder rb = rp.MakeBuilder(3, 4); - rb.Push(RESULT_SUCCESS); - rb.Push(ret); - rb.Push(total_received); - rb.PushStaticBuffer(std::move(output_buff), 0); - rb.PushStaticBuffer(std::move(addr_buff), 1); + ctx.RunAsync( + [parallel_data](Kernel::HLERequestContext& ctx) { + sockaddr src_addr; + socklen_t src_addr_len = sizeof(src_addr); + CTRSockAddr ctr_src_addr; + if (parallel_data->addr_len > 0) { + // Only get src adr if input adr available + parallel_data->ret = + ::recvfrom(parallel_data->fd_info.socket_fd, + reinterpret_cast(parallel_data->output_buff.data()), + parallel_data->len, parallel_data->flags, &src_addr, &src_addr_len); + if (parallel_data->ret >= 0 && src_addr_len > 0) { + ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); + std::memcpy(parallel_data->addr_buff.data(), &ctr_src_addr, + parallel_data->addr_len); + } + } else { + parallel_data->ret = + ::recvfrom(parallel_data->fd_info.socket_fd, + reinterpret_cast(parallel_data->output_buff.data()), + parallel_data->len, parallel_data->flags, NULL, 0); + parallel_data->addr_buff.resize(0); + } + parallel_data->recv_error = (parallel_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; + return 0; + }, + [parallel_data](Kernel::HLERequestContext& ctx) { +#ifdef _WIN32 + if (parallel_data->dont_wait && parallel_data->was_blocking) { + parallel_data->own->SetSocketBlocking(parallel_data->fd_info, true); + } +#endif + s32 total_received = parallel_data->ret; + if (parallel_data->ret == SOCKET_ERROR_VALUE) { + parallel_data->ret = TranslateError(parallel_data->recv_error); + total_received = 0; + } + + // Write only the data we received to avoid overwriting parts of the buffer with zeros + parallel_data->output_buff.resize(total_received); + + IPC::RequestBuilder rb(ctx, 0x08, 3, 4); + rb.Push(RESULT_SUCCESS); + rb.Push(parallel_data->ret); + rb.Push(total_received); + rb.PushStaticBuffer(std::move(parallel_data->output_buff), 0); + rb.PushStaticBuffer(std::move(parallel_data->addr_buff), 1); + }, + needs_parallel); } void SOC_U::Poll(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/soc/soc_u.h b/src/core/hle/service/soc/soc_u.h index 4c56d31525..56329e1ed9 100644 --- a/src/core/hle/service/soc/soc_u.h +++ b/src/core/hle/service/soc/soc_u.h @@ -61,8 +61,12 @@ private: static constexpr u32 SOC_SOL_CONFIG = 0xFFFE; static constexpr u32 SOC_SOL_SOCKET = 0xFFFF; + static constexpr int SOC_TTL_DEFAULT = 64; + static const std::unordered_map> sockopt_map; static std::pair TranslateSockOpt(int level, int opt); + static void TranslateSockOptDataToPlatform(std::vector& out, const std::vector& in, + int platform_level, int platform_opt); bool GetSocketBlocking(const SocketHolder& socket_holder); u32 SetSocketBlocking(SocketHolder& socket_holder, bool blocking);