diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index b01d6e031..b4611c748 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -2,71 +2,204 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/ipc.h" #include "core/hle/service/http_c.h" namespace Service { namespace HTTP { -const Interface::FunctionInfo FunctionTable[] = { - {0x00010044, nullptr, "Initialize"}, - {0x00020082, nullptr, "CreateContext"}, - {0x00030040, nullptr, "CloseContext"}, - {0x00040040, nullptr, "CancelConnection"}, - {0x00050040, nullptr, "GetRequestState"}, - {0x00060040, nullptr, "GetDownloadSizeState"}, - {0x00070040, nullptr, "GetRequestError"}, - {0x00080042, nullptr, "InitializeConnectionSession"}, - {0x00090040, nullptr, "BeginRequest"}, - {0x000A0040, nullptr, "BeginRequestAsync"}, - {0x000B0082, nullptr, "ReceiveData"}, - {0x000C0102, nullptr, "ReceiveDataTimeout"}, - {0x000D0146, nullptr, "SetProxy"}, - {0x000E0040, nullptr, "SetProxyDefault"}, - {0x000F00C4, nullptr, "SetBasicAuthorization"}, - {0x00100080, nullptr, "SetSocketBufferSize"}, - {0x001100C4, nullptr, "AddRequestHeader"}, - {0x001200C4, nullptr, "AddPostDataAscii"}, - {0x001300C4, nullptr, "AddPostDataBinary"}, - {0x00140082, nullptr, "AddPostDataRaw"}, - {0x00150080, nullptr, "SetPostDataType"}, - {0x001600C4, nullptr, "SendPostDataAscii"}, - {0x00170144, nullptr, "SendPostDataAsciiTimeout"}, - {0x001800C4, nullptr, "SendPostDataBinary"}, - {0x00190144, nullptr, "SendPostDataBinaryTimeout"}, - {0x001A0082, nullptr, "SendPostDataRaw"}, - {0x001B0102, nullptr, "SendPOSTDataRawTimeout"}, - {0x001C0080, nullptr, "SetPostDataEncoding"}, - {0x001D0040, nullptr, "NotifyFinishSendPostData"}, - {0x001E00C4, nullptr, "GetResponseHeader"}, - {0x001F0144, nullptr, "GetResponseHeaderTimeout"}, - {0x00200082, nullptr, "GetResponseData"}, - {0x00210102, nullptr, "GetResponseDataTimeout"}, - {0x00220040, nullptr, "GetResponseStatusCode"}, - {0x002300C0, nullptr, "GetResponseStatusCodeTimeout"}, - {0x00240082, nullptr, "AddTrustedRootCA"}, - {0x00250080, nullptr, "AddDefaultCert"}, - {0x00260080, nullptr, "SelectRootCertChain"}, - {0x002700C4, nullptr, "SetClientCert"}, - {0x002B0080, nullptr, "SetSSLOpt"}, - {0x002C0080, nullptr, "SetSSLClearOpt"}, - {0x002D0000, nullptr, "CreateRootCertChain"}, - {0x002E0040, nullptr, "DestroyRootCertChain"}, - {0x002F0082, nullptr, "RootCertChainAddCert"}, - {0x00300080, nullptr, "RootCertChainAddDefaultCert"}, - {0x00310080, nullptr, "RootCertChainRemoveCert"}, - {0x00320084, nullptr, "OpenClientCertContext"}, - {0x00330040, nullptr, "OpenDefaultClientCertContext"}, - {0x00340040, nullptr, "CloseClientCertContext"}, - {0x00350186, nullptr, "SetDefaultProxy"}, - {0x00360000, nullptr, "ClearDNSCache"}, - {0x00370080, nullptr, "SetKeepAlive"}, - {0x003800C0, nullptr, "SetPostDataTypeSize"}, - {0x00390000, nullptr, "Finalize"}, +namespace ErrCodes { +enum { + InvalidRequestMethod = 32, + InvalidContext = 102, }; - -HTTP_C::HTTP_C() { - Register(FunctionTable); } +const ResultCode ERROR_CONTEXT_ERROR = // 0xD8A0A066 + ResultCode(ErrCodes::InvalidContext, 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(); + shared_memory = rp.PopObject(); + if (shared_memory) { + shared_memory->name = "HTTP_C:shared_memory"; + } + + 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) { + IPC::RequestParser rp(ctx, 0x2, 2, 2); + const u32 url_size = rp.Pop(); + RequestMethod method = rp.PopEnum(); + Kernel::MappedBuffer& buffer = rp.PopMappedBuffer(); + + // Copy the buffer into a string without the \0 at the end of the buffer + std::string url(url_size, '\0'); + buffer.Read(&url[0], 0, url_size - 1); + + LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, + static_cast(method)); + + 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.PushMappedBuffer(buffer); + return; + } + + contexts.emplace(++context_counter, Context()); + contexts[context_counter].url = std::move(url); + contexts[context_counter].method = method; + contexts[context_counter].state = RequestState::NotStarted; + // TODO(Subv): Find a correct default value for this field. + contexts[context_counter].socket_buffer_size = 0; + contexts[context_counter].handle = context_counter; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push(context_counter); + rb.PushMappedBuffer(buffer); +} + +void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x3, 1, 0); + + u32 context_handle = rp.Pop(); + + LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); + + 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); + return; + } + + // TODO(Subv): What happens if you try to close a context that's currently being used? + ASSERT(itr->second.state == RequestState::NotStarted); + + contexts.erase(itr); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x11, 3, 4); + const u32 context_handle = rp.Pop(); + const u32 name_size = rp.Pop(); + const u32 value_size = rp.Pop(); + const std::vector name_buffer = rp.PopStaticBuffer(); + Kernel::MappedBuffer& value_buffer = rp.PopMappedBuffer(); + + // 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 + 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); + 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; + }) == itr->second.headers.end()); + + itr->second.headers.emplace_back(name, value); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(value_buffer); + + LOG_WARNING(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, + context_handle); +} + +HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { + static const FunctionInfo functions[] = { + {0x00010044, &HTTP_C::Initialize, "Initialize"}, + {0x00020082, &HTTP_C::CreateContext, "CreateContext"}, + {0x00030040, &HTTP_C::CloseContext, "CloseContext"}, + {0x00040040, nullptr, "CancelConnection"}, + {0x00050040, nullptr, "GetRequestState"}, + {0x00060040, nullptr, "GetDownloadSizeState"}, + {0x00070040, nullptr, "GetRequestError"}, + {0x00080042, nullptr, "InitializeConnectionSession"}, + {0x00090040, nullptr, "BeginRequest"}, + {0x000A0040, nullptr, "BeginRequestAsync"}, + {0x000B0082, nullptr, "ReceiveData"}, + {0x000C0102, nullptr, "ReceiveDataTimeout"}, + {0x000D0146, nullptr, "SetProxy"}, + {0x000E0040, nullptr, "SetProxyDefault"}, + {0x000F00C4, nullptr, "SetBasicAuthorization"}, + {0x00100080, nullptr, "SetSocketBufferSize"}, + {0x001100C4, &HTTP_C::AddRequestHeader, "AddRequestHeader"}, + {0x001200C4, nullptr, "AddPostDataAscii"}, + {0x001300C4, nullptr, "AddPostDataBinary"}, + {0x00140082, nullptr, "AddPostDataRaw"}, + {0x00150080, nullptr, "SetPostDataType"}, + {0x001600C4, nullptr, "SendPostDataAscii"}, + {0x00170144, nullptr, "SendPostDataAsciiTimeout"}, + {0x001800C4, nullptr, "SendPostDataBinary"}, + {0x00190144, nullptr, "SendPostDataBinaryTimeout"}, + {0x001A0082, nullptr, "SendPostDataRaw"}, + {0x001B0102, nullptr, "SendPOSTDataRawTimeout"}, + {0x001C0080, nullptr, "SetPostDataEncoding"}, + {0x001D0040, nullptr, "NotifyFinishSendPostData"}, + {0x001E00C4, nullptr, "GetResponseHeader"}, + {0x001F0144, nullptr, "GetResponseHeaderTimeout"}, + {0x00200082, nullptr, "GetResponseData"}, + {0x00210102, nullptr, "GetResponseDataTimeout"}, + {0x00220040, nullptr, "GetResponseStatusCode"}, + {0x002300C0, nullptr, "GetResponseStatusCodeTimeout"}, + {0x00240082, nullptr, "AddTrustedRootCA"}, + {0x00250080, nullptr, "AddDefaultCert"}, + {0x00260080, nullptr, "SelectRootCertChain"}, + {0x002700C4, nullptr, "SetClientCert"}, + {0x00290080, nullptr, "SetClientCertContext"}, + {0x002A0040, nullptr, "GetSSLError"}, + {0x002B0080, nullptr, "SetSSLOpt"}, + {0x002C0080, nullptr, "SetSSLClearOpt"}, + {0x002D0000, nullptr, "CreateRootCertChain"}, + {0x002E0040, nullptr, "DestroyRootCertChain"}, + {0x002F0082, nullptr, "RootCertChainAddCert"}, + {0x00300080, nullptr, "RootCertChainAddDefaultCert"}, + {0x00310080, nullptr, "RootCertChainRemoveCert"}, + {0x00320084, nullptr, "OpenClientCertContext"}, + {0x00330040, nullptr, "OpenDefaultClientCertContext"}, + {0x00340040, nullptr, "CloseClientCertContext"}, + {0x00350186, nullptr, "SetDefaultProxy"}, + {0x00360000, nullptr, "ClearDNSCache"}, + {0x00370080, nullptr, "SetKeepAlive"}, + {0x003800C0, nullptr, "SetPostDataTypeSize"}, + {0x00390000, nullptr, "Finalize"}, + }; + RegisterHandlers(functions); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared()->InstallAsService(service_manager); +} } // namespace HTTP } // namespace Service diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index cff279c02..8b77fb8af 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -4,19 +4,178 @@ #pragma once +#include +#include +#include +#include +#include +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/service.h" namespace Service { namespace HTTP { -class HTTP_C final : public Interface { +enum class RequestMethod : u8 { + None = 0x0, + Get = 0x1, + Post = 0x2, + Head = 0x3, + Put = 0x4, + Delete = 0x5, + PostEmpty = 0x6, + PutEmpty = 0x7, +}; + +/// The number of request methods, any valid method must be less than this. +constexpr u32 TotalRequestMethods = 8; + +enum class RequestState : u8 { + NotStarted = 0x1, // Request has not started yet. + InProgress = 0x5, // Request in progress, sending request over the network. + ReadyToDownloadContent = 0x7, // Ready to download the content. (needs verification) + ReadyToDownload = 0x8, // Ready to download? + TimedOut = 0xA, // Request timed out? +}; + +/// Represents a client certificate along with its private key, stored as a byte array of DER data. +/// There can only be at most one client certificate context attached to an HTTP context at any +/// given time. +struct ClientCertContext { + u32 handle; + std::vector certificate; + std::vector private_key; +}; + +/// Represents a root certificate chain, it contains a list of DER-encoded certificates for +/// verifying HTTP requests. An HTTP context can have at most one root certificate chain attached to +/// it, but the chain may contain an arbitrary number of certificates in it. +struct RootCertChain { + struct RootCACert { + u32 handle; + std::vector certificate; + }; + + u32 handle; + std::vector certificates; +}; + +/// Represents an HTTP context. +class Context final { +public: + Context() = default; + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + + Context(Context&& other) = default; + Context& operator=(Context&&) = default; + + struct Proxy { + std::string url; + std::string username; + std::string password; + u16 port; + }; + + struct BasicAuth { + std::string username; + std::string password; + }; + + struct RequestHeader { + RequestHeader(std::string name, std::string value) : name(name), value(value){}; + std::string name; + std::string value; + }; + + struct PostData { + // TODO(Subv): Support Binary and Raw POST elements. + std::string name; + std::string value; + }; + + struct SSLConfig { + u32 options; + std::weak_ptr client_cert_ctx; + std::weak_ptr root_ca_chain; + }; + + u32 handle; + std::string url; + RequestMethod method; + RequestState state = RequestState::NotStarted; + boost::optional proxy; + boost::optional basic_auth; + SSLConfig ssl_config{}; + u32 socket_buffer_size; + std::vector headers; + std::vector post_data; +}; + +class HTTP_C final : public ServiceFramework { public: HTTP_C(); - std::string GetPortName() const override { - return "http:C"; - } +private: + /** + * HTTP_C::Initialize service function + * Inputs: + * 1 : POST buffer size + * 2 : 0x20 + * 3 : 0x0 (Filled with process ID by ARM11 Kernel) + * 4 : 0x0 + * 5 : POST buffer memory block handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void Initialize(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::CreateContext service function + * Inputs: + * 1 : URL buffer size, including null-terminator + * 2 : RequestMethod + * 3 : (URLSize << 4) | 10 + * 4 : URL data pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : HTTP context handle + */ + void CreateContext(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::CreateContext service function + * Inputs: + * 1 : Context handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void CloseContext(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::AddRequestHeader service function + * Inputs: + * 1 : Context handle + * 2 : Header name buffer size, including null-terminator. + * 3 : Header value buffer size, including null-terminator. + * 4 : (HeaderNameSize<<14) | 0xC02 + * 5 : Header name data pointer + * 6 : (HeaderValueSize<<4) | 10 + * 7 : Header value data pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void AddRequestHeader(Kernel::HLERequestContext& ctx); + + Kernel::SharedPtr shared_memory = nullptr; + + std::unordered_map contexts; + u32 context_counter = 0; + + std::unordered_map client_certs; + u32 client_certs_counter = 0; }; +void InstallInterfaces(SM::ServiceManager& service_manager); + } // namespace HTTP } // namespace Service diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index f596d6d48..a06bd495e 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -256,7 +256,7 @@ void Init(std::shared_ptr& sm) { QTM::InstallInterfaces(*sm); CSND::InstallInterfaces(*sm); - AddService(new HTTP::HTTP_C); + HTTP::InstallInterfaces(*sm); PM::InstallInterfaces(*sm); SOC::InstallInterfaces(*sm); SSL::InstallInterfaces(*sm);