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);