diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ebedcb710..6930e0485 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -65,7 +65,8 @@ set(SRCS hle/service/hid/hid.cpp hle/service/hid/hid_spvr.cpp hle/service/hid/hid_user.cpp - hle/service/http_c.cpp + hle/service/http/http.cpp + hle/service/http/http_c.cpp hle/service/ir/ir.cpp hle/service/ir/ir_rst.cpp hle/service/ir/ir_u.cpp @@ -177,7 +178,8 @@ set(HEADERS hle/service/hid/hid.h hle/service/hid/hid_spvr.h hle/service/hid/hid_user.h - hle/service/http_c.h + hle/service/http/http.h + hle/service/http/http_c.h hle/service/ir/ir.h hle/service/ir/ir_rst.h hle/service/ir/ir_u.h diff --git a/src/core/hle/service/http/http.cpp b/src/core/hle/service/http/http.cpp new file mode 100644 index 000000000..aab63ba3a --- /dev/null +++ b/src/core/hle/service/http/http.cpp @@ -0,0 +1,119 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/string_util.h" + +#include "core/hle/service/http/http.h" +#include "core/hle/service/http/http_c.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace HTTP { + +std::unordered_map> context_map; +ContextHandle next_handle; + +static int WriteBuf(u8 *data, size_t size, size_t nmemb, std::mutex *mutex, std::vector *buffer) { + std::lock_guard lock(*mutex); + buffer->reserve(buffer->size() + size * nmemb); + buffer->insert(buffer->end(), data, data + size * nmemb); + return static_cast(size * nmemb); +} + +static int HdrWriter(u8 *data, size_t size, size_t nmemb, HttpContext *context) { + if (context == nullptr || context->should_quit) + return -1; + return WriteBuf(data, size, nmemb, &context->mutex, &context->response_hdrs); +} + +static int DataWriter(u8 *data, size_t size, size_t nmemb, HttpContext *context) { + if (context == nullptr || context->should_quit) + return -1; + return WriteBuf(data, size, nmemb, &context->mutex, &context->response_data); +} + +HttpContext::~HttpContext() { + curl_slist_free_all(request_hdrs); +} + +void MakeRequest(HttpContext* context) { + CURL* connection = curl_easy_init(); + CURLcode res; + { + std::lock_guard lock(context->mutex); + + context->state = REQUEST_STATE_IN_PROGRESS; + res = curl_easy_setopt(connection, CURLOPT_URL, context->url.c_str()); + switch (context->req_type) { + case REQUEST_TYPE_GET: + res = curl_easy_setopt(connection, CURLOPT_HTTPGET, 1); + break; + case REQUEST_TYPE_POST: + case REQUEST_TYPE_POST_: + res = curl_easy_setopt(connection, CURLOPT_POST, 1); + break; + case REQUEST_TYPE_PUT: + case REQUEST_TYPE_PUT_: + res = curl_easy_setopt(connection, CURLOPT_UPLOAD, 1); + break; + case REQUEST_TYPE_DELETE: + // TODO + break; + case REQUEST_TYPE_HEAD: + res = curl_easy_setopt(connection, CURLOPT_NOBODY, 1); + break; + } + + res = curl_easy_setopt(connection, CURLOPT_HEADERFUNCTION, HdrWriter); + res = curl_easy_setopt(connection, CURLOPT_WRITEFUNCTION, DataWriter); + res = curl_easy_setopt(connection, CURLOPT_HEADERDATA, context); + res = curl_easy_setopt(connection, CURLOPT_WRITEDATA, context); + res = curl_easy_setopt(connection, CURLOPT_HTTPHEADER, context->request_hdrs); + } + + res = curl_easy_perform(connection); + { + std::lock_guard lock(context->mutex); + + res = curl_easy_getinfo(connection, CURLINFO_RESPONSE_CODE, &context->response_code); + res = curl_easy_getinfo(connection, CURLINFO_SIZE_DOWNLOAD, &context->downloaded_size); + res = curl_easy_getinfo(connection, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &context->download_size); + + context->state = REQUEST_STATE_READY; + } + + curl_easy_cleanup(connection); +} + +void AddRequestHeader(std::string name, std::string value, curl_slist** hdr_list) { + std::string header = Common::StringFromFormat("%s: %s", name.c_str(), value.c_str()); + *hdr_list = curl_slist_append(*hdr_list, header.c_str()); +} + +void Init() { + AddService(new HTTP_C_Interface); + + curl_global_init(CURL_GLOBAL_DEFAULT); +} + +void ClearInstance() { + for (auto& pair : context_map) { + pair.second->should_quit = true; + if (pair.second->req_thread != nullptr) + pair.second->req_thread->join(); + } + + context_map.clear(); + next_handle = 0; +} + +void Shutdown() { + curl_global_cleanup(); + ClearInstance(); +} + +} // namespace HTTP +} // namespace Service diff --git a/src/core/hle/service/http/http.h b/src/core/hle/service/http/http.h new file mode 100644 index 000000000..db12e11a6 --- /dev/null +++ b/src/core/hle/service/http/http.h @@ -0,0 +1,74 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include "core/hle/result.h" + +struct curl_slist; + +namespace Service { +namespace HTTP { + +typedef u32 ContextHandle; + +enum RequestType : u32 { + REQUEST_TYPE_NONE = 0, + REQUEST_TYPE_GET = 1, + REQUEST_TYPE_POST = 2, + REQUEST_TYPE_HEAD = 3, + REQUEST_TYPE_PUT = 4, + REQUEST_TYPE_DELETE = 5, + REQUEST_TYPE_POST_ = 6, + REQUEST_TYPE_PUT_ = 7 +}; + +enum RequestState : u32 { + REQUEST_STATE_DEFAULT, // TODO + REQUEST_STATE_IN_PROGRESS = 5, + REQUEST_STATE_READY = 8, +}; + +struct HttpContext { + std::mutex mutex; + bool should_quit; + + RequestState state; + std::unique_ptr req_thread; + + std::string url; + RequestType req_type; + curl_slist* request_hdrs; + + std::vector response_hdrs; + std::vector response_data; + long response_code; + double download_size; + double downloaded_size; + + ~HttpContext(); +}; + +extern std::unordered_map> context_map; +extern ContextHandle next_handle; + +void MakeRequest(HttpContext* context); +void AddRequestHeader(std::string name, std::string value, curl_slist** hdr_list); + +/// Initialize the HTTP service +void Init(); + +void ClearInstance(); + +/// Shutdown the HTTP service +void Shutdown(); + +} // namespace HTTP +} // namespace Service diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http/http_c.cpp similarity index 63% rename from src/core/hle/service/http_c.cpp rename to src/core/hle/service/http/http_c.cpp index 2cb7692f5..355ca537d 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -4,87 +4,21 @@ #include #include -#include - -#include #include "core/hle/hle.h" -#include "core/hle/service/http_c.h" +#include "core/hle/service/http/http.h" +#include "core/hle/service/http/http_c.h" #include "common/logging/log.h" #include "common/make_unique.h" #include "common/string_util.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HTTP_C - -namespace HTTP_C { - -typedef u32 ContextHandle; - -enum RequestType : u32 { - REQUEST_TYPE_NONE = 0, - REQUEST_TYPE_GET = 1, - REQUEST_TYPE_POST = 2, - REQUEST_TYPE_HEAD = 3, - REQUEST_TYPE_PUT = 4, - REQUEST_TYPE_DELETE = 5, - REQUEST_TYPE_POST_ = 6, - REQUEST_TYPE_PUT_ = 7 -}; - -enum RequestState : u32 { - REQUEST_STATE_DEFAULT, // TODO - REQUEST_STATE_IN_PROGRESS = 5, - REQUEST_STATE_READY = 7, -}; - -struct HttpContext { - std::mutex mutex; - - RequestState state; - - std::string url; - RequestType req_type; - curl_slist* request_hdrs; - - std::vector response_hdrs; - std::vector response_data; - long response_code; - double download_size; - double downloaded_size; - - ~HttpContext() { - curl_slist_free_all(request_hdrs); - } -}; - -static std::unordered_map> context_map; -static ContextHandle next_handle; - -static int writer_hdrs(u8* data, size_t size, size_t nmemb, HttpContext* context) { - if (context == nullptr) - return 0; - std::lock_guard lock(context->mutex); - context->response_hdrs.reserve(context->response_hdrs.size() + size * nmemb); - context->response_hdrs.insert(context->response_hdrs.end(), data, data + size * nmemb); - return (int)(size * nmemb); -} - -static int writer_data(u8* data, size_t size, size_t nmemb, HttpContext* context) { - if (context == nullptr) - return 0; - std::lock_guard lock(context->mutex); - context->response_data.reserve(context->response_data.size() + size * nmemb); - context->response_data.insert(context->response_data.end(), data, data + size * nmemb); - return (int)(size * nmemb); -} +namespace Service { +namespace HTTP { static void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - curl_global_init(CURL_GLOBAL_DEFAULT); - cmd_buff[1] = RESULT_SUCCESS.raw; } @@ -105,7 +39,6 @@ static void CreateContext(Service::Interface* self) { context->req_type = req_type; context->url = url; - context_map.emplace(next_handle, std::move(context)); cmd_buff[1] = RESULT_SUCCESS.raw; @@ -118,17 +51,36 @@ static void CloseContext(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ContextHandle handle = static_cast(cmd_buff[1]); -// auto map_it = request_map.find(handle); -// if (map_it == request_map.end()) { -// cmd_buff[1] = -1; // TODO: Find proper result code for invalid handle -// return; -// } - // TODO: wait for thread to finish! + auto map_it = context_map.find(handle); + if (map_it == context_map.end()) { + cmd_buff[1] = -1; // TODO: Find proper result code for invalid handle + return; + } + + map_it->second->should_quit = true; + if (map_it->second->req_thread != nullptr) + map_it->second->req_thread->join(); + context_map.erase(handle); cmd_buff[1] = RESULT_SUCCESS.raw; } +static void CancelConnection(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + ContextHandle handle = static_cast(cmd_buff[1]); + + auto map_it = context_map.find(handle); + if (map_it == context_map.end()) { + cmd_buff[1] = -1; // TODO: Find proper result code for invalid handle + return; + } + map_it->second->should_quit = true; + + cmd_buff[1] = RESULT_SUCCESS.raw; +} + static void GetRequestState(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -169,56 +121,6 @@ static void GetDownloadSizeState(Service::Interface* self) { return; } -static void MakeRequest(HttpContext* context) { - CURL* connection = curl_easy_init(); - CURLcode res; - { - std::lock_guard lock(context->mutex); - - context->state = REQUEST_STATE_IN_PROGRESS; - res = curl_easy_setopt(connection, CURLOPT_URL, context->url.c_str()); - switch (context->req_type) { - case REQUEST_TYPE_GET: - res = curl_easy_setopt(connection, CURLOPT_HTTPGET, 1); - break; - case REQUEST_TYPE_POST: - case REQUEST_TYPE_POST_: - res = curl_easy_setopt(connection, CURLOPT_POST, 1); - break; - case REQUEST_TYPE_PUT: - case REQUEST_TYPE_PUT_: - res = curl_easy_setopt(connection, CURLOPT_UPLOAD, 1); - break; - case REQUEST_TYPE_DELETE: - // TODO - break; - case REQUEST_TYPE_HEAD: - res = curl_easy_setopt(connection, CURLOPT_NOBODY, 1); - break; - } - - res = curl_easy_setopt(connection, CURLOPT_HEADERFUNCTION, writer_hdrs); - res = curl_easy_setopt(connection, CURLOPT_WRITEFUNCTION, writer_data); - res = curl_easy_setopt(connection, CURLOPT_HEADERDATA, context); - res = curl_easy_setopt(connection, CURLOPT_WRITEDATA, context); - - curl_easy_setopt(connection, CURLOPT_HTTPHEADER, context->request_hdrs); - } - - res = curl_easy_perform(connection); - { - std::lock_guard lock(context->mutex); - - curl_easy_getinfo(connection, CURLINFO_RESPONSE_CODE, &context->response_code); - curl_easy_getinfo(connection, CURLINFO_SIZE_DOWNLOAD, &context->downloaded_size); - curl_easy_getinfo(connection, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &context->download_size); - - context->state = REQUEST_STATE_READY; - } - - curl_easy_cleanup(connection); -} - static void BeginRequest(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -231,9 +133,8 @@ static void BeginRequest(Service::Interface* self) { } HttpContext* context = map_it->second.get(); - - std::thread req_thread(MakeRequest, context); - req_thread.detach(); + std::lock_guard lock(context->mutex); + context->req_thread = Common::make_unique(MakeRequest, context); cmd_buff[1] = RESULT_SUCCESS.raw; } @@ -242,7 +143,7 @@ static void ReceiveData(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ContextHandle handle = static_cast(cmd_buff[1]); - u32 buf_size = cmd_buff[2]; + u32 buf_size = cmd_buff[2]; u8* buffer = Memory::GetPointer(cmd_buff[4]); auto map_it = context_map.find(handle); @@ -268,15 +169,12 @@ static void AddRequestHeader(Service::Interface* self) { ContextHandle handle = static_cast(cmd_buff[1]); u32 hdr_name_len = cmd_buff[2]; u32 hdr_val_len = cmd_buff[3]; - u32 hdr_name_buf = cmd_buff[5]; - u32 hdr_val_buf = cmd_buff[7]; + const char* hdr_name_buf = reinterpret_cast(Memory::GetPointer(cmd_buff[5])); + const char* hdr_val_buf = reinterpret_cast(Memory::GetPointer(cmd_buff[7])); // TODO: something is NULL // TODO: header value is empty // TODO: header name is empty - std::string header = Common::StringFromFormat("%s: %s", - std::string(reinterpret_cast(Memory::GetPointer(hdr_name_buf)), hdr_name_len).c_str(), - std::string(reinterpret_cast(Memory::GetPointer(hdr_val_buf)), hdr_val_len).c_str()); auto map_it = context_map.find(handle); if (map_it == context_map.end()) { @@ -285,7 +183,9 @@ static void AddRequestHeader(Service::Interface* self) { } std::lock_guard lock(map_it->second->mutex); - map_it->second->request_hdrs = curl_slist_append(map_it->second->request_hdrs, header.c_str()); + AddRequestHeader(std::string(hdr_name_buf, hdr_name_len).c_str(), + std::string(hdr_val_buf, hdr_val_len).c_str(), + &map_it->second->request_hdrs); cmd_buff[1] = RESULT_SUCCESS.raw; } @@ -300,6 +200,7 @@ static void GetResponseStatusCode(Service::Interface* self) { return; } + // TODO: Verify behavior while (true) { std::lock_guard lock(map_it->second->mutex); if (map_it->second->state == REQUEST_STATE_READY) @@ -317,9 +218,7 @@ static void GetResponseStatusCode(Service::Interface* self) { static void Finalize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - context_map.clear(); - next_handle = 0; - curl_global_cleanup(); + ClearInstance(); cmd_buff[1] = RESULT_SUCCESS.raw; } @@ -328,7 +227,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00010044, Initialize, "Initialize"}, {0x00020082, CreateContext, "CreateContext"}, {0x00030040, CloseContext, "CloseContext"}, - {0x00040040, nullptr, "CancelConnection"}, + {0x00040040, CancelConnection, "CancelConnection"}, {0x00050040, GetRequestState, "GetRequestState"}, {0x00060040, GetDownloadSizeState, "GetDownloadSizeState"}, {0x00070040, nullptr, "GetRequestError"}, @@ -370,8 +269,9 @@ const Interface::FunctionInfo FunctionTable[] = { //////////////////////////////////////////////////////////////////////////////////////////////////// // Interface class -Interface::Interface() { +HTTP_C_Interface::HTTP_C_Interface() { Register(FunctionTable); } +} } // namespace diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http/http_c.h similarity index 58% rename from src/core/hle/service/http_c.h rename to src/core/hle/service/http/http_c.h index 5ea3d1df3..69f09004f 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http/http_c.h @@ -6,14 +6,12 @@ #include "core/hle/service/service.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace HTTP_C +namespace Service { +namespace HTTP { -namespace HTTP_C { - -class Interface : public Service::Interface { +class HTTP_C_Interface : public Service::Interface { public: - Interface(); + HTTP_C_Interface(); std::string GetPortName() const override { return "http:C"; @@ -21,3 +19,4 @@ public: }; } // namespace +} diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 64185c62e..71fcdaa68 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -23,7 +23,6 @@ #include "core/hle/service/frd_u.h" #include "core/hle/service/gsp_gpu.h" #include "core/hle/service/gsp_lcd.h" -#include "core/hle/service/http_c.h" #include "core/hle/service/ldr_ro.h" #include "core/hle/service/mic_u.h" #include "core/hle/service/ndm_u.h" @@ -43,6 +42,7 @@ #include "core/hle/service/fs/archive.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/hid/hid.h" +#include "core/hle/service/http/http.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ptm/ptm.h" @@ -115,6 +115,7 @@ void Init() { Service::APT::Init(); Service::PTM::Init(); Service::HID::Init(); + Service::HTTP::Init(); Service::IR::Init(); AddService(new AC_U::Interface); @@ -133,7 +134,6 @@ void Init() { AddService(new FRD_U::Interface); AddService(new GSP_GPU::Interface); AddService(new GSP_LCD::Interface); - AddService(new HTTP_C::Interface); AddService(new LDR_RO::Interface); AddService(new MIC_U::Interface); AddService(new NDM_U::Interface);