From 28c296a13b84e200df1774b0e725b5f8ac0cf5b6 Mon Sep 17 00:00:00 2001 From: Subv Date: Wed, 25 Jul 2018 14:59:29 -0500 Subject: [PATCH 1/5] Services/HTTP: Corrected some error codes and added a few new ones. Use the session data to store information about the HTTP session state. This is based on reverse engineering of the HTTP module. The AddRequestHeader function is still mostly incorrect, this will be fixed in follow up PRs --- src/core/hle/service/http_c.cpp | 71 +++++++++++++++++++++++++++++---- src/core/hle/service/http_c.h | 44 +++++++++++++++----- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index b4611c748..78364dccc 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -11,13 +11,18 @@ namespace HTTP { namespace ErrCodes { enum { + TooManyContexts = 26, InvalidRequestMethod = 32, - InvalidContext = 102, + + /// This error is returned in multiple situations: when trying to initialize an + /// already-initialized session, or when using the wrong context handle in a context-bound + /// session + SessionStateError = 102, }; } -const ResultCode ERROR_CONTEXT_ERROR = // 0xD8A0A066 - ResultCode(ErrCodes::InvalidContext, ErrorModule::HTTP, ErrorSummary::InvalidState, +const ResultCode ERROR_STATE_ERROR = // 0xD8A0A066 + ResultCode(ErrCodes::SessionStateError, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent); void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { @@ -29,12 +34,23 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { shared_memory->name = "HTTP_C:shared_memory"; } + LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {}", shmem_size); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (session_data->initialized) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + session_data->initialized = true; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // This returns 0xd8a0a046 if no network connection is available. // Just assume we are always connected. rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {}", shmem_size); } void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { @@ -50,11 +66,38 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, static_cast(method)); + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + // This command can only be called without a bound session. + if (session_data->current_http_context != boost::none) { + LOG_ERROR(Service_HTTP, "Command called with a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + rb.PushMappedBuffer(buffer); + return; + } + + static constexpr size_t MaxConcurrentHTTPContexts = 8; + if (session_data->num_http_contexts >= MaxConcurrentHTTPContexts) { + // There can only be 8 HTTP contexts open at the same time for any particular session. + LOG_ERROR(Service_HTTP, "Tried to open too many HTTP contexts"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultCode(ErrCodes::TooManyContexts, ErrorModule::HTTP, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + rb.PushMappedBuffer(buffer); + return; + } + if (method == RequestMethod::None || static_cast(method) >= TotalRequestMethods) { LOG_ERROR(Service_HTTP, "invalid request method={}", static_cast(method)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(ERROR_CONTEXT_ERROR); + rb.Push(ResultCode(ErrCodes::InvalidRequestMethod, ErrorModule::HTTP, + ErrorSummary::InvalidState, ErrorLevel::Permanent)); rb.PushMappedBuffer(buffer); return; } @@ -67,6 +110,8 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { contexts[context_counter].socket_buffer_size = 0; contexts[context_counter].handle = context_counter; + session_data->num_http_contexts++; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); rb.Push(context_counter); @@ -80,10 +125,17 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + ASSERT_MSG(session_data->current_http_context == boost::none, + "Unimplemented CloseContext on context-bound session"); + auto itr = contexts.find(context_handle); if (itr == contexts.end()) { + // The real HTTP module just silently fails in this case. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ERROR_CONTEXT_ERROR); + rb.Push(RESULT_SUCCESS); LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); return; } @@ -91,7 +143,10 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { // TODO(Subv): What happens if you try to close a context that's currently being used? ASSERT(itr->second.state == RequestState::NotStarted); + // TODO(Subv): Make sure that only the session that created the context can close it. + contexts.erase(itr); + session_data->num_http_contexts--; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -115,7 +170,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { auto itr = contexts.find(context_handle); if (itr == contexts.end()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ERROR_CONTEXT_ERROR); + rb.Push(ERROR_STATE_ERROR); LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); return; } diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index 8b77fb8af..c01f66970 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -41,7 +41,8 @@ enum class RequestState : u8 { /// There can only be at most one client certificate context attached to an HTTP context at any /// given time. struct ClientCertContext { - u32 handle; + using Handle = u32; + Handle handle; std::vector certificate; std::vector private_key; }; @@ -51,17 +52,21 @@ struct ClientCertContext { /// it, but the chain may contain an arbitrary number of certificates in it. struct RootCertChain { struct RootCACert { - u32 handle; + using Handle = u32; + Handle handle; std::vector certificate; }; - u32 handle; + using Handle = u32; + Handle handle; std::vector certificates; }; /// Represents an HTTP context. class Context final { public: + using Handle = u32; + Context() = default; Context(const Context&) = delete; Context& operator=(const Context&) = delete; @@ -99,7 +104,7 @@ public: std::weak_ptr root_ca_chain; }; - u32 handle; + Handle handle; std::string url; RequestMethod method; RequestState state = RequestState::NotStarted; @@ -111,7 +116,22 @@ public: std::vector post_data; }; -class HTTP_C final : public ServiceFramework { +struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { + /// The HTTP context that is currently bound to this session, this can be empty if no context + /// has been bound. Certain commands can only be called on a session with a bound context. + boost::optional current_http_context; + + /// Number of HTTP contexts that are currently opened in this session. + u32 num_http_contexts = 0; + /// Number of ClientCert contexts that are currently opened in this session. + u32 num_client_certs = 0; + + /// Whether this session has been initialized in some way, be it via Initialize or + /// InitializeConnectionSession. + bool initialized = false; +}; + +class HTTP_C final : public ServiceFramework { public: HTTP_C(); @@ -168,11 +188,17 @@ private: Kernel::SharedPtr shared_memory = nullptr; - std::unordered_map contexts; - u32 context_counter = 0; + /// The next handle number to use when a new HTTP context is created. + Context::Handle context_counter = 0; - std::unordered_map client_certs; - u32 client_certs_counter = 0; + /// The next handle number to use when a new ClientCert context is created. + ClientCertContext::Handle client_certs_counter = 0; + + /// Global list of HTTP contexts currently opened. + std::unordered_map contexts; + + /// Global list of ClientCert contexts currently opened. + std::unordered_map client_certs; }; void InstallInterfaces(SM::ServiceManager& service_manager); From 03294ce6b48d3b6558ac174e3c3b8f495b081eb7 Mon Sep 17 00:00:00 2001 From: Subv Date: Wed, 25 Jul 2018 16:13:47 -0500 Subject: [PATCH 2/5] Services/HTTP: Implemented the InitializeConnectionSession function. --- src/core/hle/service/http_c.cpp | 50 +++++++++++++++++++++++++++++++-- src/core/hle/service/http_c.h | 11 ++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 78364dccc..02f0cf88f 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -13,6 +13,7 @@ namespace ErrCodes { enum { TooManyContexts = 26, InvalidRequestMethod = 32, + ContextNotFound = 100, /// This error is returned in multiple situations: when trying to initialize an /// already-initialized session, or when using the wrong context handle in a context-bound @@ -28,7 +29,7 @@ const ResultCode ERROR_STATE_ERROR = // 0xD8A0A066 void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x1, 1, 4); const u32 shmem_size = rp.Pop(); - rp.PopPID(); + u32 pid = rp.PopPID(); shared_memory = rp.PopObject(); if (shared_memory) { shared_memory->name = "HTTP_C:shared_memory"; @@ -53,6 +54,38 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x8, 1, 2); + const u32 context_handle = rp.Pop(); + u32 pid = rp.PopPID(); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (session_data->initialized) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + // TODO(Subv): Check that the input PID matches the PID that created the context. + auto itr = contexts.find(context_handle); + if (itr == contexts.end()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrCodes::ContextNotFound, ErrorModule::HTTP, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + return; + } + + session_data->initialized = true; + // Bind the context to the current session. + session_data->current_http_context = context_handle; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HTTP, "called, context_id={}", context_handle); +} + void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x2, 2, 2); const u32 url_size = rp.Pop(); @@ -69,6 +102,13 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); + if (!session_data->initialized) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ERROR_STATE_ERROR); + rb.PushMappedBuffer(buffer); + return; + } + // This command can only be called without a bound session. if (session_data->current_http_context != boost::none) { LOG_ERROR(Service_HTTP, "Command called with a bound context"); @@ -128,6 +168,12 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); + if (!session_data->initialized) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + ASSERT_MSG(session_data->current_http_context == boost::none, "Unimplemented CloseContext on context-bound session"); @@ -200,7 +246,7 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x00050040, nullptr, "GetRequestState"}, {0x00060040, nullptr, "GetDownloadSizeState"}, {0x00070040, nullptr, "GetRequestError"}, - {0x00080042, nullptr, "InitializeConnectionSession"}, + {0x00080042, &HTTP_C::InitializeConnectionSession, "InitializeConnectionSession"}, {0x00090040, nullptr, "BeginRequest"}, {0x000A0040, nullptr, "BeginRequestAsync"}, {0x000B0082, nullptr, "ReceiveData"}, diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index c01f66970..59f5e973f 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -171,6 +171,17 @@ private: */ void CloseContext(Kernel::HLERequestContext& ctx); + /** + * HTTP_C::InitializeConnectionSession service function + * Inputs: + * 1 : HTTP context handle + * 2 : 0x20, processID translate-header for the ARM11-kernel + * 3 : processID set by the ARM11-kernel + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void InitializeConnectionSession(Kernel::HLERequestContext& ctx); + /** * HTTP_C::AddRequestHeader service function * Inputs: From d19dbe84193f0f45579fc932f531b7e4114c3801 Mon Sep 17 00:00:00 2001 From: Subv Date: Wed, 25 Jul 2018 16:48:04 -0500 Subject: [PATCH 3/5] Services/HTTP: Added error handling to AddRequestHeader --- src/core/hle/service/http_c.cpp | 45 ++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 02f0cf88f..f4e65eb7b 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -11,6 +11,7 @@ namespace HTTP { namespace ErrCodes { enum { + InvalidRequestState = 22, TooManyContexts = 26, InvalidRequestMethod = 32, ContextNotFound = 100, @@ -209,19 +210,49 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { // Copy the name_buffer into a string without the \0 at the end const std::string name(name_buffer.begin(), name_buffer.end() - 1); - // Copy thr value_buffer into a string without the \0 at the end + // Copy the value_buffer into a string without the \0 at the end std::string value(value_size - 1, '\0'); value_buffer.Read(&value[0], 0, value_size - 1); - auto itr = contexts.find(context_handle); - if (itr == contexts.end()) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ERROR_STATE_ERROR); - LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); + rb.PushMappedBuffer(value_buffer); + return; + } + + // This command can only be called with a bound context + if (session_data->current_http_context == boost::none) { + LOG_ERROR(Service_HTTP, "Command called without a bound context"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, + ErrorSummary::Internal, ErrorLevel::Permanent)); + rb.PushMappedBuffer(value_buffer); + return; + } + + if (session_data->current_http_context != context_handle) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ERROR_STATE_ERROR); + rb.PushMappedBuffer(value_buffer); + return; + } + + auto itr = contexts.find(context_handle); + ASSERT(itr != contexts.end()); + + if (itr->second.state != RequestState::NotStarted) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultCode(ErrCodes::InvalidRequestState, ErrorModule::HTTP, + ErrorSummary::InvalidState, ErrorLevel::Permanent)); + rb.PushMappedBuffer(value_buffer); return; } - ASSERT(itr->second.state == RequestState::NotStarted); ASSERT(std::find_if(itr->second.headers.begin(), itr->second.headers.end(), [&name](const Context::RequestHeader& m) -> bool { return m.name == name; @@ -233,7 +264,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(value_buffer); - LOG_WARNING(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, + LOG_DEBUG(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, context_handle); } From 79db1f8b497d4ed7ce4373a35d3baadae02e62a3 Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 3 Aug 2018 15:24:26 -0500 Subject: [PATCH 4/5] Service/HTTP: Log the PIDs in the Initialize functions. --- src/core/hle/service/http_c.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index f4e65eb7b..9087b9a59 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -36,7 +36,7 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { shared_memory->name = "HTTP_C:shared_memory"; } - LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {}", shmem_size); + LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {} pid: {}", shmem_size, pid); auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); @@ -57,7 +57,7 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x8, 1, 2); - const u32 context_handle = rp.Pop(); + const Context::Handle context_handle = rp.Pop(); u32 pid = rp.PopPID(); auto* session_data = GetSessionData(ctx.Session()); @@ -84,7 +84,7 @@ void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HTTP, "called, context_id={}", context_handle); + LOG_DEBUG(Service_HTTP, "called, context_id={} pid={}", context_handle, pid); } void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { @@ -265,7 +265,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { rb.PushMappedBuffer(value_buffer); LOG_DEBUG(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, - context_handle); + context_handle); } HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { From b72e7d48e8daea89af070bc18064c50c73a7f2ff Mon Sep 17 00:00:00 2001 From: Subv Date: Wed, 8 Aug 2018 08:32:46 -0500 Subject: [PATCH 5/5] Service/HTTP: Added logs to some of the error conditions. --- src/core/hle/service/http_c.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 9087b9a59..a4ef456c5 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -42,6 +42,7 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { ASSERT(session_data); if (session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to initialize an already initialized session"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERROR_STATE_ERROR); return; @@ -64,6 +65,7 @@ void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { ASSERT(session_data); if (session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to initialize an already initialized session"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERROR_STATE_ERROR); return; @@ -104,6 +106,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { ASSERT(session_data); if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to create a context on an uninitialized session"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ERROR_STATE_ERROR); rb.PushMappedBuffer(buffer); @@ -170,6 +173,7 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { ASSERT(session_data); if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to close a context on an uninitialized session"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERROR_STATE_ERROR); return; @@ -218,6 +222,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { ASSERT(session_data); if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Tried to add a request header on an uninitialized session"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ERROR_STATE_ERROR); rb.PushMappedBuffer(value_buffer); @@ -236,6 +241,10 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { } if (session_data->current_http_context != context_handle) { + LOG_ERROR(Service_HTTP, + "Tried to add a request header on a mismatched session input context={} session " + "context={}", + context_handle, session_data->current_http_context.get()); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ERROR_STATE_ERROR); rb.PushMappedBuffer(value_buffer); @@ -246,6 +255,8 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { ASSERT(itr != contexts.end()); if (itr->second.state != RequestState::NotStarted) { + LOG_ERROR(Service_HTTP, + "Tried to add a request header on a context that has already been started."); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultCode(ErrCodes::InvalidRequestState, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent));