diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index b5d128819e..17c1e1a2a0 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -305,6 +305,99 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void HTTP_C::ReceiveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + [[maybe_unused]] const u32 buffer_size = rp.Pop(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called"); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to make a request on an uninitialized session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + // This command can only be called with a bound context + if (!session_data->current_http_context) { + LOG_ERROR(Service_HTTP, "Tried to make a request without a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + return; + } + + if (session_data->current_http_context != context_handle) { + LOG_ERROR( + Service_HTTP, + "Tried to make a request on a mismatched session input context={} session context={}", + context_handle, *session_data->current_http_context); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.request_future.wait(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void HTTP_C::ReceiveDataTimeout(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + [[maybe_unused]] const u32 buffer_size = rp.Pop(); + const u64 timeout = rp.Pop(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called, timeout={}", timeout); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to make a request on an uninitialized session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + // This command can only be called with a bound context + if (!session_data->current_http_context) { + LOG_ERROR(Service_HTTP, "Tried to make a request without a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + return; + } + + if (session_data->current_http_context != context_handle) { + LOG_ERROR( + Service_HTTP, + "Tried to make a request on a mismatched session input context={} session context={}", + context_handle, *session_data->current_http_context); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.request_future.wait_for(std::chrono::nanoseconds(timeout)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 url_size = rp.Pop(); @@ -569,6 +662,101 @@ void HTTP_C::AddPostDataAscii(Kernel::HLERequestContext& ctx) { rb.PushMappedBuffer(value_buffer); } +void HTTP_C::GetResponseStatusCode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + + LOG_INFO(Service_HTTP, "called"); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to make a request on an uninitialized session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + // This command can only be called with a bound context + if (!session_data->current_http_context) { + LOG_ERROR(Service_HTTP, "Tried to make a request without a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + return; + } + + if (session_data->current_http_context != context_handle) { + LOG_ERROR( + Service_HTTP, + "Tried to make a request on a mismatched session input context={} session context={}", + context_handle, *session_data->current_http_context); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.request_future.wait(); + const u32 response_code = itr->second.response.status; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(response_code); +} + +void HTTP_C::GetResponseStatusCodeTimeout(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + const u64 timeout = rp.Pop(); + + LOG_INFO(Service_HTTP, "called, timeout={}", timeout); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to make a request on an uninitialized session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + // This command can only be called with a bound context + if (!session_data->current_http_context) { + LOG_ERROR(Service_HTTP, "Tried to make a request without a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + return; + } + + if (session_data->current_http_context != context_handle) { + LOG_ERROR( + Service_HTTP, + "Tried to make a request on a mismatched session input context={} session context={}", + context_handle, *session_data->current_http_context); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + itr->second.request_future.wait_for(std::chrono::nanoseconds(timeout)); + const u32 response_code = itr->second.response.status; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(response_code); +} + void HTTP_C::SetClientCertContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 context_handle = rp.Pop(); @@ -812,6 +1000,45 @@ void HTTP_C::Finalize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_HTTP, "(STUBBED) called"); } +void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const Context::Handle context_handle = rp.Pop(); + + LOG_INFO(Service_HTTP, "called"); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to make a request on an uninitialized session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + // 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. + u32 content_length = 0; + const bool is_complete = itr->second.request_future.wait_for(std::chrono::milliseconds(0)) == + std::future_status::ready; + if (is_complete) { + const auto& headers = itr->second.response.headers; + const auto& it = headers.find("Content-Length"); + if (it != headers.end()) { + content_length = std::stoi(it->second); + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(content_length); + rb.Push(content_length); +} + void HTTP_C::DecryptClCertA() { static constexpr u32 iv_length = 16; @@ -895,13 +1122,13 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x0003, &HTTP_C::CloseContext, "CloseContext"}, {0x0004, nullptr, "CancelConnection"}, {0x0005, nullptr, "GetRequestState"}, - {0x0006, nullptr, "GetDownloadSizeState"}, + {0x0006, &HTTP_C::GetDownloadSizeState, "GetDownloadSizeState"}, {0x0007, nullptr, "GetRequestError"}, {0x0008, &HTTP_C::InitializeConnectionSession, "InitializeConnectionSession"}, {0x0009, &HTTP_C::BeginRequest, "BeginRequest"}, {0x000A, &HTTP_C::BeginRequestAsync, "BeginRequestAsync"}, - {0x000B, nullptr, "ReceiveData"}, - {0x000C, nullptr, "ReceiveDataTimeout"}, + {0x000B, &HTTP_C::ReceiveData, "ReceiveData"}, + {0x000C, &HTTP_C::ReceiveDataTimeout, "ReceiveDataTimeout"}, {0x000D, nullptr, "SetProxy"}, {0x000E, nullptr, "SetProxyDefault"}, {0x000F, nullptr, "SetBasicAuthorization"}, @@ -923,8 +1150,8 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x001F, nullptr, "GetResponseHeaderTimeout"}, {0x0020, nullptr, "GetResponseData"}, {0x0021, nullptr, "GetResponseDataTimeout"}, - {0x0022, nullptr, "GetResponseStatusCode"}, - {0x0023, nullptr, "GetResponseStatusCodeTimeout"}, + {0x0022, &HTTP_C::GetResponseStatusCode, "GetResponseStatusCode"}, + {0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"}, {0x0024, nullptr, "AddTrustedRootCA"}, {0x0025, nullptr, "AddDefaultCert"}, {0x0026, nullptr, "SelectRootCertChain"}, diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index 490e066481..e99df25033 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -288,6 +288,17 @@ private: */ void CloseContext(Kernel::HLERequestContext& ctx); + /** + * HTTP_C::GetDownloadSizeState service function + * Inputs: + * 1 : Context handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Total content data downloaded so far + * 3 : Total content size from the "Content-Length" response header + */ + void GetDownloadSizeState(Kernel::HLERequestContext& ctx); + /** * HTTP_C::InitializeConnectionSession service function * Inputs: @@ -302,7 +313,7 @@ private: /** * HTTP_C::BeginRequest service function * Inputs: - * 1 : Context handle + * 1 : Context handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ @@ -311,12 +322,37 @@ private: /** * HTTP_C::BeginRequestAsync service function * Inputs: - * 1 : Context handle + * 1 : Context handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ void BeginRequestAsync(Kernel::HLERequestContext& ctx); + /** + * HTTP_C::ReceiveData service function + * Inputs: + * 1 : Context handle + * 2 : Buffer size + * 3 : (OutSize<<4) | 12 + * 4 : Output data pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void ReceiveData(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::ReceiveDataTimeout service function + * Inputs: + * 1 : Context handle + * 2 : Buffer size + * 3-4 : u64 nanoseconds delay + * 5 : (OutSize<<4) | 12 + * 6 : Output data pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void ReceiveDataTimeout(Kernel::HLERequestContext& ctx); + /** * HTTP_C::AddRequestHeader service function * Inputs: @@ -347,6 +383,27 @@ private: */ void AddPostDataAscii(Kernel::HLERequestContext& ctx); + /** + * HTTP_C::GetResponseStatusCode service function + * Inputs: + * 1 : Context handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : HTTP response status code + */ + void GetResponseStatusCode(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::GetResponseStatusCode service function + * Inputs: + * 1 : Context handle + * 2-3 : u64 nanoseconds timeout + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : HTTP response status code + */ + void GetResponseStatusCodeTimeout(Kernel::HLERequestContext& ctx); + /** * HTTP_C::SetClientCertContext service function * Inputs: