diff --git a/Core/HLE/sceHttp.cpp b/Core/HLE/sceHttp.cpp index a0461c8792..ab30fe3519 100644 --- a/Core/HLE/sceHttp.cpp +++ b/Core/HLE/sceHttp.cpp @@ -35,7 +35,53 @@ #include "Common/Net/HTTPClient.h" -static std::vector> httpObjects; +// Templates, connections and requests are numbered separately, with the kind in the top byte +// (1, 0x01000001, 0x02000001). A new object takes the lowest free number (tests/net/http/ids). +enum HttpIdKind { + HTTP_ID_TEMPLATE = 0, + HTTP_ID_CONNECTION = 1, + HTTP_ID_REQUEST = 2, + HTTP_ID_KIND_COUNT, +}; + +static std::vector> httpObjects[HTTP_ID_KIND_COUNT]; + +static HTTPTemplate *GetHttpObject(int id) { + const u32 kind = (u32)id >> 24; + const u32 number = (u32)id & 0x00FFFFFF; + if (kind >= HTTP_ID_KIND_COUNT || number == 0 || number > httpObjects[kind].size()) + return nullptr; + return httpObjects[kind][number - 1].get(); +} + +static bool IsHttpId(int id, HttpIdKind kind) { + return ((u32)id >> 24) == (u32)kind && GetHttpObject(id) != nullptr; +} + +static HTTPRequest *GetHttpRequest(int id) { + return IsHttpId(id, HTTP_ID_REQUEST) ? static_cast(GetHttpObject(id)) : nullptr; +} + +static int AddHttpObject(HttpIdKind kind, std::shared_ptr obj) { + auto &objects = httpObjects[kind]; + size_t index = 0; + while (index < objects.size() && objects[index]) + index++; + if (index == objects.size()) + objects.push_back(obj); + else + objects[index] = obj; + return ((int)kind << 24) | (int)(index + 1); +} + +static void RemoveHttpObject(int id) { + httpObjects[(u32)id >> 24][((u32)id & 0x00FFFFFF) - 1].reset(); +} + +static void ClearHttpObjects() { + for (auto &objects : httpObjects) + objects.clear(); +} static std::mutex httpLock; bool httpInited = false; @@ -65,13 +111,14 @@ int HTTPTemplate::addRequestHeader(const char* name, const char* value, u32 mode } int HTTPTemplate::removeRequestHeader(const char* name) { - requestHeaders_.erase(name); + if (requestHeaders_.erase(name) == 0) + return SCE_HTTP_ERROR_NOT_FOUND; return 0; } HTTPConnection::HTTPConnection(int templateID, const char* hostString, const char* scheme, u32 port, int enableKeepalive) { // Copy base data as initial base value for this - HTTPTemplate::operator=(*httpObjects[templateID - 1LL]); + HTTPTemplate::operator=(*GetHttpObject(templateID)); // Initialize this->templateID = templateID; @@ -84,7 +131,7 @@ HTTPConnection::HTTPConnection(int templateID, const char* hostString, const cha HTTPRequest::HTTPRequest(int connectionID, int method, const char *url, u64 contentLength, net::ResolveFunc customResolver) : client(customResolver) { // Copy base data as initial base value for this // Since dynamic_cast/dynamic_pointer_cast/typeid requires RTTI to be enabled (ie. /GR instead of /GR- on msvc, enabled by default on most compilers), so we can only use static_cast here - HTTPConnection::operator=(static_cast(*httpObjects[connectionID - 1LL])); + HTTPConnection::operator=(*static_cast(GetHttpObject(connectionID))); // Initialize this->connectionID = connectionID; @@ -103,10 +150,6 @@ HTTPRequest::~HTTPRequest() { } int HTTPRequest::getResponseContentLength() { - // FIXME: Will sceHttpGetContentLength returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; - entityLength_ = -1; // FIXME: should we default to 0 instead? for (std::string& line : responseHeaders_) { if (startsWithNoCase(line, "Content-Length")) { @@ -129,17 +172,10 @@ int HTTPRequest::abortRequest() { } int HTTPRequest::getStatusCode() { - // FIXME: Will sceHttpGetStatusCode returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; return responseCode_; } int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) { - // FIXME: Will sceHttpGetAllHeader returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; - const char* const delim = "\r\n"; std::ostringstream imploded; std::copy(responseHeaders_.begin(), responseHeaders_.end(), std::ostream_iterator(imploded, delim)); @@ -191,9 +227,6 @@ int HTTPRequest::getAllResponseHeaders(u32 headerAddrPtr, u32 headerSizePtr) { } int HTTPRequest::readData(u32 destDataPtr, u32 size) { - // FIXME: Will sceHttpReadData returns an error if the request was not sent yet? - //if (progress_.progress == 0.0f) - // return SCE_HTTP_ERROR_BEFORE_SEND; u32 sz = std::min(size, (u32)responseContent_.size()); if (sz > 0) { Memory::MemcpyUnchecked(destDataPtr, responseContent_.c_str(), sz); @@ -289,6 +322,7 @@ int HTTPRequest::sendRequest(u32 postDataPtr, u32 postDataSize) { return SCE_HTTP_ERROR_ABORTED; } + sent_ = true; return 0; } @@ -301,23 +335,19 @@ void __HttpShutdown() { httpsInited = false; httpCacheInited = false; - for (const auto& it : httpObjects) { - if (it->className() == name_HTTPRequest) + for (const auto& it : httpObjects[HTTP_ID_REQUEST]) { + if (it) (static_cast(it.get()))->abortRequest(); } - httpObjects.clear(); + ClearHttpObjects(); } -// id: ID of the template or connection +// id: ID of a template int sceHttpSetResolveRetry(int id, int retryCount) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!IsHttpId(id, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& conn = httpObjects[id - 1LL]; - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - - conn->setResolveRetry(retryCount); + GetHttpObject(id)->setResolveRetry(retryCount); return hleLogDebug(Log::sceNet, 0); } @@ -327,9 +357,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) { return hleLogError(Log::sceNet, SCE_HTTP_ERROR_ALREADY_INITED, "http already inited"); std::lock_guard guard(httpLock); - httpObjects.clear(); - // Reserve at least 1 element to prevent ::begin() from returning null when no element has been added yet - httpObjects.reserve(1); + ClearHttpObjects(); httpInited = true; return hleLogDebug(Log::sceNet, 0); } @@ -337,7 +365,7 @@ static int sceHttpInit(MIPSState *mips, int poolSize) { static int sceHttpEnd() { WARN_LOG(Log::sceNet, "UNTESTED sceHttpEnd()"); std::lock_guard guard(httpLock); - httpObjects.clear(); + ClearHttpObjects(); httpInited = false; return hleLogDebug(Log::sceNet, 0); } @@ -369,13 +397,15 @@ static u32 sceHttpGetProxy(u32 id, u32 activateFlagPtr, u32 modePtr, u32 proxyHo static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetStatusCode(%d, %x)", requestID, statusCodePtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(statusCodePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int status = req->getStatusCode(); @@ -389,13 +419,15 @@ static int sceHttpGetStatusCode(int requestID, u32 statusCodePtr) { // Games will repeatedly called sceHttpReadData until it returns (the size read into the data buffer) 0 // FIXME: sceHttpReadData seems to be blocking current thread, since hleDelayResult can make Download progressbar to moves progressively instead of instantly jump to 100% static int sceHttpReadData(int requestID, u32 dataPtr, u32 dataSize) { - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); @@ -420,13 +452,12 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { if (!httpInited) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_INIT, "http not initialized yet"); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (dataSize > 0 && !Memory::IsValidRange(dataPtr, dataSize)) return hleLogError(Log::sceNet, -1, "invalid arg"); // SCE_HTTP_ERROR_INVALID_VALUE - - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); // Internally try to connect, and get response headers (at least the status code?) int retval = req->sendRequest(dataPtr, dataSize); return hleLogDebug(Log::sceNet, retval); @@ -434,54 +465,53 @@ static int sceHttpSendRequest(int requestID, u32 dataPtr, u32 dataSize) { static int sceHttpDeleteRequest(int requestID) { std::lock_guard guard(httpLock); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + if (!IsHttpId(requestID, HTTP_ID_REQUEST)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[requestID - 1LL]->className() != name_HTTPRequest) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - httpObjects.erase(httpObjects.begin() + requestID - 1); + RemoveHttpObject(requestID); return hleLogDebug(Log::sceNet, 0); } // id: ID of the template, connection or request static int sceHttpDeleteHeader(int id, const char *name) { - if (id <= 0 || id > (int)httpObjects.size()) + HTTPTemplate *obj = GetHttpObject(id); + if (!obj) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + if (!name) + return hleLogError(Log::sceNet, -1, "invalid name"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); - return req->removeRequestHeader(name); + return obj->removeRequestHeader(name); } static int sceHttpDeleteConnection(int connectionID) { std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + for (const auto &it : httpObjects[HTTP_ID_REQUEST]) { + if (it && static_cast(it.get())->getConnectionID() == connectionID) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS, "connection has requests"); + } - httpObjects.erase(httpObjects.begin() + connectionID - 1); + RemoveHttpObject(connectionID); return hleLogDebug(Log::sceNet, 0); } -// id: ID of the template, connection or request +// id: ID of the template or connection static int sceHttpSetConnectTimeOut(int id, u32 timeout) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - auto& conn = httpObjects[id - 1LL]; - conn->setConnectTimeout(timeout); + GetHttpObject(id)->setConnectTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } -// id: ID of the template, connection or request +// id: ID of the template or connection static int sceHttpSetSendTimeOut(int id, u32 timeout) { - if (id <= 0 || id > (int)httpObjects.size()) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - auto& conn = httpObjects[id - 1LL]; - conn->setSendTimeout(timeout); + GetHttpObject(id)->setSendTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -533,17 +563,11 @@ static int sceHttpsDisableOption(int id) { static int sceHttpCreateRequest(int connectionID, int method, const char *path, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequest(%d, %d, %s, %llx)", connectionID, method, safe_string(path), contentLength); std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + // Any method is accepted here. + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); - - httpObjects.emplace_back(std::make_shared(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared(connectionID, method, path ? path : "", contentLength, &ProcessHostnameWithInfraDNS)); return hleLogDebug(Log::sceNet, retid); } @@ -551,16 +575,12 @@ static int sceHttpCreateRequest(int connectionID, int method, const char *path, static int sceHttpCreateConnection(int templateID, const char *hostString, const char *scheme, u32 port, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnection(%d, %s, %s, %d, %d)", templateID, safe_string(hostString), safe_string(scheme), port, enableKeepalive); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); // TODO: Look up hostString in DNS here. - httpObjects.emplace_back(std::make_shared(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared(templateID, hostString ? hostString : "", scheme ? scheme : "", port, enableKeepalive)); return hleLogDebug(Log::sceNet, retid); } @@ -577,32 +597,31 @@ static int sceHttpGetNetworkErrno(int request, u32 errNumPtr) { // id: ID of the template, connection or request static int sceHttpAddExtraHeader(int id, const char *name, const char *value, int unknown) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpAddExtraHeader(%d, %s, %s, %d)", id, safe_string(name), safe_string(value), unknown); - if (id <= 0 || id > (int)httpObjects.size()) + HTTPTemplate *obj = GetHttpObject(id); + if (!obj) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); + if (!name || !value) + return hleLogError(Log::sceNet, -1, "invalid header"); // SCE_HTTP_ERROR_INVALID_VALUE - const auto& req = (HTTPRequest*)httpObjects[id - 1LL].get(); - return req->addRequestHeader(name, value, unknown); + return obj->addRequestHeader(name, value, unknown); } static int sceHttpAbortRequest(int requestID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpAbortRequest(%d)", requestID); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); return req->abortRequest(); } static int sceHttpDeleteTemplate(int templateID) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpDeleteTemplate(%d)", templateID); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) + // Its connections stay usable. + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - httpObjects.erase(httpObjects.begin() + templateID - 1); + RemoveHttpObject(templateID); return hleLogDebug(Log::sceNet, 0); } @@ -611,17 +630,13 @@ static int sceHttpSetMallocFunction(u32 mallocFuncPtr, u32 freeFuncPtr, u32 real return 0; } -// id: ID of the template or connection +// id: ID of a template static int sceHttpSetResolveTimeOut(int id, u32 timeout) { ERROR_LOG(Log::sceNet, "UNIMPL sceHttpSetResolveTimeOut(%d, %d)", id, timeout); - if (id <= 0 || id > (int)httpObjects.size()) + if (!IsHttpId(id, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - const auto& conn = httpObjects[id - 1LL]; - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - conn->setResolveTimeout(timeout); + GetHttpObject(id)->setResolveTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -680,8 +695,7 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt // Reporting to find more games to be tested WARN_LOG_REPORT_ONCE(sceHttpCreateTemplate, Log::sceNet, "UNTESTED sceHttpCreateTemplate(%s, %d, %d)", safe_string(userAgent), httpVer, autoProxyConf); std::lock_guard guard(httpLock); - httpObjects.push_back(std::make_shared(userAgent? userAgent:"", httpVer, autoProxyConf)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_TEMPLATE, std::make_shared(userAgent? userAgent:"", httpVer, autoProxyConf)); return hleLogDebug(Log::sceNet, retid); } @@ -689,53 +703,39 @@ static int sceHttpCreateTemplate(MIPSState *mips, const char *userAgent, int htt static int sceHttpCreateRequestWithURL(int connectionID, int method, const char *url, u64 contentLength) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateRequestWithURL(%d, %d, %s, %llx)", connectionID, method, safe_string(url), contentLength); std::lock_guard guard(httpLock); - if (connectionID <= 0 || connectionID > (int)httpObjects.size()) + // Any method is accepted here. + if (!IsHttpId(connectionID, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - if (httpObjects[connectionID - 1LL]->className() != name_HTTPConnection) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (method < PSPHttpMethod::PSP_HTTP_METHOD_GET || method > PSPHttpMethod::PSP_HTTP_METHOD_HEAD) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_UNKNOWN_METHOD, "unknown method"); - Url baseURL(url ? url : ""); if (!baseURL.Valid()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); - httpObjects.emplace_back(std::make_shared(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_REQUEST, std::make_shared(connectionID, method, url ? url : "", contentLength, &ProcessHostnameWithInfraDNS)); return hleLogDebug(Log::sceNet, retid); } static int sceHttpCreateConnectionWithURL(int templateID, const char *url, int enableKeepalive) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpCreateConnectionWithURL(%d, %s, %d)", templateID, safe_string(url), enableKeepalive); std::lock_guard guard(httpLock); - if (templateID <= 0 || templateID > (int)httpObjects.size()) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - - if (httpObjects[templateID - 1LL]->className() != name_HTTPTemplate) + if (!IsHttpId(templateID, HTTP_ID_TEMPLATE)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); Url baseURL(url ? url: ""); if (!baseURL.Valid()) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_URL, "invalid url"); - httpObjects.emplace_back(std::make_shared(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive)); - int retid = (int)httpObjects.size(); + int retid = AddHttpObject(HTTP_ID_CONNECTION, std::make_shared(templateID, baseURL.Host().c_str(), baseURL.Protocol().c_str(), baseURL.Port(), enableKeepalive)); return hleLogDebug(Log::sceNet, retid); } // id: ID of the template or connection static int sceHttpSetRecvTimeOut(int id, u32 timeout) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpSetRecvTimeOut(%d, %d)", id, timeout); - if (id <= 0 || id > (int)httpObjects.size()) + if (!IsHttpId(id, HTTP_ID_TEMPLATE) && !IsHttpId(id, HTTP_ID_CONNECTION)) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); - const auto& conn = httpObjects[id - 1LL]; - if (!(conn->className() == name_HTTPTemplate || conn->className() == name_HTTPConnection)) - return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id (%s)", conn->className()); - - conn->setRecvTimeout(timeout); + GetHttpObject(id)->setRecvTimeout(timeout); return hleLogDebug(Log::sceNet, 0); } @@ -743,7 +743,8 @@ static int sceHttpSetRecvTimeOut(int id, u32 timeout) { // Note: Megaman PoweredUp seems to have an invalid address stored at the headerAddrPtr location, may be the game expecting us (network library) to give them a valid header address? static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizePtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetAllHeader(%d, %x, %x)", requestID, headerAddrPtr, headerSizePtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(headerAddrPtr, 4)) @@ -752,7 +753,8 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP if (!Memory::IsValidRange(headerSizePtr, 4)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int retval = req->getAllResponseHeaders(headerAddrPtr, headerSizePtr); @@ -762,13 +764,15 @@ static int sceHttpGetAllHeader(int requestID, u32 headerAddrPtr, u32 headerSizeP // FIXME: contentLength is SceULong64 but this contentLengthPtr argument should be a 32bit pointer instead of 64bit, right? static int sceHttpGetContentLength(int requestID, u32 contentLengthPtr) { WARN_LOG(Log::sceNet, "UNTESTED sceHttpGetContentLength(%d, %x)", requestID, contentLengthPtr); - if (requestID <= 0 || requestID > (int)httpObjects.size()) + HTTPRequest *req = GetHttpRequest(requestID); + if (!req) return hleLogError(Log::sceNet, SCE_HTTP_ERROR_INVALID_ID, "invalid id"); if (!Memory::IsValidRange(contentLengthPtr, 8)) return hleLogError(Log::sceNet, -1, "invalid arg"); //SCE_HTTP_ERROR_INVALID_VALUE; - const auto& req = (HTTPRequest*)httpObjects[requestID - 1LL].get(); + if (!req->wasSent()) + return hleLogError(Log::sceNet, SCE_HTTP_ERROR_BEFORE_SEND, "not sent"); // FIXME: According to JPCSP, try to connect the request first //req->connect(); int len = req->getResponseContentLength(); diff --git a/Core/HLE/sceHttp.h b/Core/HLE/sceHttp.h index 8c532bc2cc..d661f425d2 100644 --- a/Core/HLE/sceHttp.h +++ b/Core/HLE/sceHttp.h @@ -52,6 +52,8 @@ enum SceHttpErrorCode { SCE_HTTP_ERROR_BAD_RESPONSE = 0x80431064, SCE_HTTP_ERROR_BEFORE_SEND = 0x80431065, SCE_HTTP_ERROR_AFTER_SEND = 0x80431066, + // sceHttpDeleteConnection, while the connection has requests (tests/net/http/ids). + SCE_HTTP_ERROR_CONNECTION_HAS_REQUESTS = 0x80431067, SCE_HTTP_ERROR_TIMEOUT = 0x80431068, SCE_HTTP_ERROR_UNKOWN_AUTH_TYPE = 0x80431069, SCE_HTTP_ERROR_INVALID_VERSION = 0x8043106A, @@ -258,6 +260,7 @@ private: u32 headerAddr_ = 0; u32 headerSize_ = 0; bool cancelled_ = false; + bool sent_ = false; int responseCode_ = -1; int entityLength_ = -1; @@ -275,6 +278,8 @@ public: void setInternalHeaderAddr(u32 addr) { headerAddr_ = addr; } int getConnectionID() { return connectionID; } + // Until a send succeeds, the calls that read the response fail with SCE_HTTP_ERROR_BEFORE_SEND. + bool wasSent() const { return sent_; } int getResponseRemainingContentLength() { return (int)responseContent_.size(); } int getResponseContentLength(); diff --git a/Core/HLE/sceNp.cpp b/Core/HLE/sceNp.cpp index 4474118a97..21fe33bb5a 100644 --- a/Core/HLE/sceNp.cpp +++ b/Core/HLE/sceNp.cpp @@ -431,14 +431,13 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_UNKNOWN, "Missing npOnlineId"); } - int result = length; - // Built here, then copied once we know it fits. - u8 staging[512]{}; + // Built here and copied out, since the buffer can be shorter than the ticket. + u8 ticketBuf[0xF0 + sizeof(SceNpTicketHeader)]{}; SceNpTicket ticket = {}; // Dummy Login ticket returned as Login response. Dummy ticket contents were taken from https://www.psdevwiki.com/ps3/X-I-5-Ticket ticket.header.version = TICKET_VER_2_1; ticket.header.size = 0xF0; // size excluding the header - u8 *buf = staging + sizeof(ticket); + u8 *buf = ticketBuf + sizeof(ticket); int ofs = 0; ofs += writeTicketParam(buf, PARAM_TYPE_STRING_ASCII, "\x4c\x47\x56\x3b\x81\x39\x4a\x22\xd8\x6b\xc1\x57\x71\x6e\xfd\xb8\xab\x63\xcc\x51", 20); // 20 random letters, token key or SceNpSignature? ofs += writeTicketU32Param(buf + ofs, PARAM_TYPE_INT, 0x0100); // a flags? @@ -464,24 +463,23 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) { ofs += writeTicketParam(buf + ofs, PARAM_TYPE_NULL); ticket.section.type = SECTION_TYPE_BODY; ticket.section.size = ofs; - memcpy(staging, &ticket, sizeof(SceNpTicket)); + memcpy(ticketBuf, &ticket, sizeof(SceNpTicket)); SceNpTicketSection footer = { SECTION_TYPE_FOOTER, 32 }; // footer section? ie. 32-bytes on version 2.1 containing 4-chars ASCII + 20-chars ASCII memcpy(buf + ofs, &footer, sizeof(SceNpTicketSection)); ofs += sizeof(footer); ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x34\xcd\x3c\xa9", 4); ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x3a\x4b\x42\x66\x92\xda\x6b\x7c\xb7\x4c\xe8\xd9\x4f\x2b\x77\x15\x91\xb8\xa4\xa9", 20); // 20 random letters, token key or SceNpSignature? - // includes Language list? (36 zero bytes, staging is zeroed) - const u32 total = (u32)sizeof(ticket) + ofs + 36; - _dbg_assert_(total <= sizeof(staging)); + // includes Language list? (36 zero bytes, left by the initializer) + _dbg_assert_(sizeof(ticket) + ofs + 36 == sizeof(ticketBuf)); - // The whole ticket gets written, even if length is shorter. - if (!Memory::IsValidRange(bufferAddr, std::max(length, total))) { + // Like JPCSP's real ticket path, copy what fits. + if (!Memory::IsValidRange(bufferAddr, length)) { return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid buffer"); } Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket"); - Memory::Memcpy(bufferAddr, staging, total, "NpAuthGetTicket"); + Memory::Memcpy(bufferAddr, ticketBuf, std::min(length, (u32)sizeof(ticketBuf)), "NpAuthGetTicket"); - result = ticket.header.size + sizeof(ticket.header); // dummy ticket is 248 bytes + const int result = (int)sizeof(ticketBuf); // dummy ticket is 248 bytes return hleLogWarning(Log::sceNet, result); } @@ -524,43 +522,119 @@ int sceNpAuthDestroyRequest(int requestId) return hleNoLog(0); } +// Tickets are big-endian, and params needn't be aligned. +static u16 ReadTicketU16(u32 addr) { + return ((u16)Memory::ReadUnchecked_U8(addr) << 8) | Memory::ReadUnchecked_U8(addr + 1); +} + +static u32 ReadTicketU32(u32 addr) { + return ((u32)ReadTicketU16(addr) << 16) | ReadTicketU16(addr + 2); +} + +// Writes one param's value into the 256-byte output, as GetTicketParam does for every param it +// walks past. A NULL param leaves the output alone, any other clears it first, and a type with no +// value (unknown ones) leaves it clear. Numbers are read at their full size whatever length the +// param gives. +static void WriteTicketParamValue(u32 bufferPtr, u16 type, u32 valuePtr, u32 length) { + const u32 PARAM_BUFFER_MAX_SIZE = 256; + if (type == PARAM_TYPE_NULL) { + return; + } + Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam"); + + u32 readSize = 0; + switch (type) { + case PARAM_TYPE_INT: readSize = 4; break; + case PARAM_TYPE_LONG: case PARAM_TYPE_DATE: readSize = 8; break; + case PARAM_TYPE_STRING: case PARAM_TYPE_STRING_ASCII: readSize = std::min(length, PARAM_BUFFER_MAX_SIZE - 1); break; + case 0x3011: readSize = 4; break; + default: return; + } + if (!Memory::IsValidRange(valuePtr, readSize)) { + return; + } + + switch (type) { + case PARAM_TYPE_INT: + Memory::WriteUnchecked_U32(ReadTicketU32(valuePtr), bufferPtr); + break; + case PARAM_TYPE_LONG: + case PARAM_TYPE_DATE: + Memory::WriteUnchecked_U64(((u64)ReadTicketU32(valuePtr) << 32) | ReadTicketU32(valuePtr + 4), bufferPtr); + break; + case 0x3011: + // A big-endian u16 followed by two bytes as they are. + Memory::WriteUnchecked_U16(ReadTicketU16(valuePtr), bufferPtr); + Memory::Memcpy(bufferPtr + 2, valuePtr + 2, 2, "NpAuthGetTicketParam"); + break; + default: + // The terminator is left by the clear. + Memory::Memcpy(bufferPtr, valuePtr, readSize, "NpAuthGetTicketParam"); + break; + } +} + +// Behaviour from tests/net/npauth/ticketparam. int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 bufferPtr) { const u32 PARAM_BUFFER_MAX_SIZE = 256; + // The type each param has to have. Param 11 is never found. + static const u16 paramTypes[NUMBER_PARAMETERS - 1] = { + PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE, PARAM_TYPE_DATE, PARAM_TYPE_LONG, PARAM_TYPE_STRING, + PARAM_TYPE_STRING_ASCII, PARAM_TYPE_STRING, PARAM_TYPE_STRING_ASCII, PARAM_TYPE_INT, PARAM_TYPE_DATE, + }; + if (!Memory::IsValidRange(bufferPtr, PARAM_BUFFER_MAX_SIZE)) { return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid buffer"); } - Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE); // JPCSP: This clear is always done, even when an error is returned - if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) { - return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT); + // Cleared even when an error is returned. + Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE, "NpAuthGetTicketParam"); + if (ticketLen < 4 || ticketLen > 0x10000) { + return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_TICKET_SIZE, "invalid ticket size"); } - if (ticketLen < (int)sizeof(SceNpTicket) || !Memory::IsValidRange(ticketBufPtr, ticketLen)) { + if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) { + return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid param"); + } + if (!Memory::IsValidRange(ticketBufPtr, ticketLen)) { return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid ticket"); } - const SceNpTicket *ticket = (const SceNpTicket *)Memory::GetPointerUnchecked(ticketBufPtr); - const u32 ticketEnd = ticketBufPtr + ticketLen; - u32 inbuf = ticketBufPtr; - inbuf += sizeof(ticket->header); - inbuf += ticket->section.size + sizeof(ticket->section); - u32 outbuf = bufferPtr; - for (int i = 0; i < paramNum; i++) { - // Only copy params that are inside the ticket and fit in the output. - if (inbuf < ticketBufPtr || inbuf + sizeof(SceNpTicketParamData) > ticketEnd) - break; - const SceNpTicketParamData *ticketParam = (const SceNpTicketParamData *)Memory::GetPointerUnchecked(inbuf); - u32 sz = (u32)sizeof(SceNpTicketParamData) + ticketParam->length; - if (sz > ticketEnd - inbuf || sz > PARAM_BUFFER_MAX_SIZE - (outbuf - bufferPtr)) - break; - Memory::MemcpyUnchecked(outbuf, inbuf, sz); - DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, i, static_cast(ticketParam->type), static_cast(ticketParam->length)); - outbuf += sz; - inbuf += sz; - if (outbuf - bufferPtr >= PARAM_BUFFER_MAX_SIZE || inbuf - ticketBufPtr >= (u32)ticketLen) - break; + // Major version in the top nibble, minor in the next byte: 2.1 and 3.0 are accepted. + const u32 version = ReadTicketU32(ticketBufPtr); + const u32 major = version >> 28; + const u32 minor = (version >> 16) & 0xFF; + if (!(major == 2 && minor == 1) && !(major == 3 && minor == 0)) { + return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_TICKET_VERSION, "unsupported ticket version %08x", version); + } + if (paramNum == NUMBER_PARAMETERS - 1) { + return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_TICKET_PARAM_NOT_FOUND); + } + // The header's size and the section's size aren't used. + if (ticketLen < (int)sizeof(SceNpTicket) || ReadTicketU16(ticketBufPtr + 8) != SECTION_TYPE_BODY) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "no body section"); } - return hleLogWarning(Log::sceNet, 0); + // Every param up to the one asked for is written out in turn, so a failure leaves the last + // one written in the buffer. + const u32 ticketEnd = ticketBufPtr + (u32)ticketLen; + u32 inbuf = ticketBufPtr + (u32)sizeof(SceNpTicket); + for (int i = 0; i <= paramNum; i++) { + if (inbuf + sizeof(SceNpTicketParamData) > ticketEnd) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "ticket ends before param %d", i); + } + const u16 type = ReadTicketU16(inbuf); + const u32 length = ReadTicketU16(inbuf + 2); + const u32 valuePtr = inbuf + (u32)sizeof(SceNpTicketParamData); + if (valuePtr + length > ticketEnd) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d runs past the ticket", i); + } + WriteTicketParamValue(bufferPtr, type, valuePtr, length); + if (type != paramTypes[i]) { + return hleLogError(Log::sceNet, SCE_NP_UTIL_ERROR_PARSER_FAILED, "param %d has type %04x", i, type); + } + inbuf = valuePtr + length; + } + return hleLogDebug(Log::sceNet, 0); } const HLEFunction sceNpAuth[] = { diff --git a/pspautotests b/pspautotests index 1a9e6c31cf..70e72391e9 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 1a9e6c31cf31411f82f8a2d659855442f6dfda11 +Subproject commit 70e72391e95b4b5bf6cd7ffd7e0259f52abf9a99 diff --git a/test.py b/test.py index 5b1a587a74..40adf4c683 100755 --- a/test.py +++ b/test.py @@ -370,6 +370,8 @@ tests_good = [ "net/inet/polledge", "net/inet/reuse", "net/inet/errnothread", + "net/http/ids", + "net/npauth/ticketparam", "net/adhoc/abandon", "net/adhoc/alerts", "net/adhoc/apctlinit",