diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index b4611c748..a4ef456c5 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -11,30 +11,82 @@ namespace HTTP { namespace ErrCodes { enum { + InvalidRequestState = 22, + TooManyContexts = 26, InvalidRequestMethod = 32, - InvalidContext = 102, + 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 + /// 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) { 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"; } + LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {} pid: {}", shmem_size, pid); + + auto* session_data = GetSessionData(ctx.Session()); + 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; + } + + 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::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x8, 1, 2); + const Context::Handle context_handle = rp.Pop(); + u32 pid = rp.PopPID(); + + auto* session_data = GetSessionData(ctx.Session()); + 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; + } + + // 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={} pid={}", context_handle, pid); } void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { @@ -50,11 +102,46 @@ 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); + + 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); + 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"); + + 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 +154,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 +169,24 @@ 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); + + 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; + } + + 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 +194,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); @@ -108,19 +214,56 @@ 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); - rb.Push(ERROR_CONTEXT_ERROR); - LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); + auto* session_data = GetSessionData(ctx.Session()); + 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); + 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) { + 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); + return; + } + + auto itr = contexts.find(context_handle); + 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)); + 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; @@ -132,8 +275,8 @@ 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, - context_handle); + LOG_DEBUG(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, + context_handle); } HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { @@ -145,7 +288,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 8b77fb8af..59f5e973f 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(); @@ -151,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: @@ -168,11 +199,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);