From 28c296a13b84e200df1774b0e725b5f8ac0cf5b6 Mon Sep 17 00:00:00 2001 From: Subv Date: Wed, 25 Jul 2018 14:59:29 -0500 Subject: [PATCH] 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);