JD2022-TU1/main/extern/UbiServices/custom/testsUnit/http/ps4/testHttpRequestPs4.cpp

316 lines
16 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour //
////////////////////////////////////////////////////////////////////////////////
#include "tests/config.h"
#include "tests/common/helpers.h"
#include "tests/fixture/memoryCheckFixture.h"
#include "testsUnit/http/ps4/mockHttpPs4Impl.h"
#include "testsUnit/mock/mockHttpRequest.h"
#include <ubiservices/core/encoding/base64.h>
#include <ubiservices/core/http/ps4/httpRequestPs4.h>
namespace US_NS {;
US_DEFINE_TEST(TestHttpRequestPS4);
#if !defined(US_DLL)
typedef LeakChecker HTTP_REQUEST_PS4_MOCK;
/// @test Checks that the SSL callback is set whether the url has to be checked and the validation is requested
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_SslServerValidation)
{
struct Local
{
// Take base64 string and decode them to obfuscated pinning keys
static SSLPinningKeyList getSslObfuscatedPinningKeys()
{
// Base64 encoded string of obfuscated pinning keys
// The keys were obfuscated using LOCAL_ObfuscatePinningKeys
const char* UBISERVICES_SSL_ALLOWED_PUBLIC_KEY_OBFUSCATED_BASE64[] =
{
"Cyd0Aw9QBSAnV1kODHQAB3AHViQDAgEkAFABXnoBIQACVAQPXwx3JwJ/JwlXJHV4cCQNVQYHAwQhBiECAAIFVAD+", // pub_star_ubi
"WnQhV1FQUycgUFpeBycEBHEHAXVUAVN3VwMOXiAEJ1IHBVNcXVgldgR+JVwGJnd5J3ldUg4MVlAhAncMCgJVBAD+", // pub1
"W3N8Vg8ABiUkBQlZBnQFVnFTA3YABgN1Vw1VASVRJAVVAlUKCwh1dQ0lJwhVcSYjJHcABAZVVAF2AnoNAAVXBQD+", // pub2
"WXJ9AgYBDid5VwFbV3ABBnMCBXMHBAMjVQQDDXoBfVFRUwEPClwlcwJwcgkCJHUncCcBAAMDDgIiVCdWAAYEVgD+", // pub3
};
SSLPinningKeyList sslObfuscatedPinningKeys;
uint32 keyCount = sizeof(UBISERVICES_SSL_ALLOWED_PUBLIC_KEY_OBFUSCATED_BASE64) / sizeof(UBISERVICES_SSL_ALLOWED_PUBLIC_KEY_OBFUSCATED_BASE64[0]);
for (uint32 i = 0; i < keyCount; i++)
{
Vector<uint8> obfuscatedBuffer;
Base64::decode(UBISERVICES_SSL_ALLOWED_PUBLIC_KEY_OBFUSCATED_BASE64[i], obfuscatedBuffer);
sslObfuscatedPinningKeys.push_back(obfuscatedBuffer);
}
return sslObfuscatedPinningKeys;
}
static SSLPinningKeyObfuscator getSslPinningKeyObfuscator()
{
// The same obfuscator as in sslCertificateValidator
const uint8 UBISERVICES_SSL_PINNING_KEY_OBFUSCATOR[] =
{
0xa0, 0x71, 0x59, 0xac, 0xf1, 0x07, 0x74, 0xde,
0x32, 0x7d, 0x1b, 0x79, 0x65, 0x02, 0x6b, 0x8c,
0x94, 0xf9, 0xc8, 0x3b, 0xda, 0x5d, 0xd9, 0x42,
0x72, 0xf0, 0x24, 0x81, 0xc4, 0xf6, 0xaf, 0x0a,
0xba, 0x0c, 0x03, 0x5e, 0xfa, 0x20, 0x30, 0x34,
0x75, 0xa8, 0x0f, 0x9f, 0xcb, 0x1a, 0xcc, 0x08,
0x38, 0x2d, 0x4c, 0x76, 0xc7, 0xf4, 0x11, 0x9d,
0x43, 0xbb, 0xd5, 0x3f, 0xbc, 0x8e, 0xd3, 0x58
};
;
return SSLPinningKeyObfuscator(
UBISERVICES_SSL_PINNING_KEY_OBFUSCATOR,
UBISERVICES_SSL_PINNING_KEY_OBFUSCATOR + sizeof(UBISERVICES_SSL_PINNING_KEY_OBFUSCATOR));
}
static void checkkSslInitialization(const StringUrl url,
bool isValidationRequested,
bool isValidationExpected,
const SSLPinningKeyList& sslPinningKeys,
const SSLPinningKeyObfuscator& sslPinningKeyObfuscator)
{
using namespace testing;
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext();
requestContext.getRequestData().m_url = url;
requestContext.getRequestData().m_sslPinningKeys = sslPinningKeys;
requestContext.getRequestData().m_sslPinningKeyObfuscator = sslPinningKeyObfuscator;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
HttpRequestPs4 request(requestContext, &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
Expectation httpsSslInit;
if (isValidationExpected)
{
// When the certificate is to be validated, the SSL callback is set
httpsSslInit = EXPECT_CALL(ps4Impl, sceHttpsSetSslCallback(CONNECTION_ID, _, _)).WillOnce(Return(SCE_OK));
}
else
{
httpsSslInit = EXPECT_CALL(ps4Impl, sceHttpsEnableOption(CONNECTION_ID, SCE_HTTPS_FLAG_SERVER_VERIFY)).WillOnce(Return(SCE_OK));
}
const SceInt32 REQUEST_ID = 6;
Expectation createRequest = EXPECT_CALL(ps4Impl, sceHttpCreateRequestWithURL2(CONNECTION_ID, _, _, _)).After(httpsSslInit).WillOnce(Return(REQUEST_ID));
Expectation setNonBlock = EXPECT_CALL(ps4Impl, sceHttpSetNonblock(REQUEST_ID, SCE_TRUE)).After(createRequest).WillOnce(Return(SCE_OK));
SceHttpEpollHandle eh = reinterpret_cast<SceHttpEpollHandle>(1); // arbitrary value to ensure eh will not be null
Expectation createEpoll = EXPECT_CALL(ps4Impl, sceHttpCreateEpoll(_, _)).After(setNonBlock).WillOnce(DoAll(SetArgPointee<1>(eh), Return(SCE_OK)));
EXPECT_CALL(ps4Impl, sceHttpSetEpoll(REQUEST_ID, _, nullptr)).After(createEpoll).WillOnce(Return(SCE_OK));
request.initialize(isValidationRequested);
Expectation unsetEpoll = EXPECT_CALL(ps4Impl, sceHttpUnsetEpoll(REQUEST_ID)).WillOnce(Return(SCE_OK));
Expectation destroyEpoll = EXPECT_CALL(ps4Impl, sceHttpDestroyEpoll(_, _)).After(unsetEpoll).WillOnce(Return(SCE_OK));
EXPECT_CALL(ps4Impl, sceHttpDeleteRequest(REQUEST_ID)).After(destroyEpoll).WillOnce(Return(SCE_OK));
}
};
const SSLPinningKeyList sslPinningKeys = Local::getSslObfuscatedPinningKeys();
const SSLPinningKeyObfuscator sslPinningKeyObfuscator = Local::getSslPinningKeyObfuscator();
// An URL that should be validated - with pinning keys => validation should be set
Local::checkkSslInitialization("https://ussdkcertvalidation.ubi.com/", true, true, sslPinningKeys, sslPinningKeyObfuscator);
// An URL that should be validated - no pinning keys => validation should NOT be set
Local::checkkSslInitialization("https://no.validation.url/", false, false, SSLPinningKeyList(), SSLPinningKeyObfuscator());
// An URL that should NOT be validated - with pinning keys => validation should NOT be set
Local::checkkSslInitialization("http://no.validation.url/", false, false, sslPinningKeys, sslPinningKeyObfuscator);
// An URL that should NOT be validated - no pinning keys => validation should NOT be set
Local::checkkSslInitialization("http://no.validation.url/", false, false, SSLPinningKeyList(), SSLPinningKeyObfuscator());
}
/// @test Checks the request initialization behavior when an error occurs during the SSL initialization
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestSslInitFail)
{
using namespace testing;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
EXPECT_CALL(ps4Impl, sceHttpsEnableOption(CONNECTION_ID, SCE_HTTPS_FLAG_SERVER_VERIFY)).WillOnce(Return(SCE_HTTP_ERROR_INSUFFICIENT_STACKSIZE));
EXPECT_CALL(ps4Impl, sceHttpCreateRequestWithURL2(_, _, _, _)).Times(0);
HttpRequestPs4 request(MockHttpRequest_BF::createDummyContext(), &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
EXPECT_FALSE(request.initialize(false));
}
/// @test Checks the request initialization behavior when an error occurs during the sce request creation
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestCreationFail)
{
using namespace testing;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
EXPECT_CALL(ps4Impl, sceHttpsEnableOption(CONNECTION_ID, SCE_HTTPS_FLAG_SERVER_VERIFY)).WillOnce(Return(SCE_OK));
EXPECT_CALL(ps4Impl, sceHttpCreateRequestWithURL2(CONNECTION_ID, _, _, _)).WillOnce(Return(SCE_HTTP_ERROR_INVALID_ID));
// The request creation fails so the request shall not be set to non blocking,
// and the request shall not be deleted (since it couldn't be created)
EXPECT_CALL(ps4Impl, sceHttpSetNonblock(_, _)).Times(0);
EXPECT_CALL(ps4Impl, sceHttpDeleteRequest(_)).Times(0);
HttpRequestPs4 request(MockHttpRequest_BF::createDummyContext(), &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
EXPECT_FALSE(request.initialize(false));
}
/// @test Checks the request initialization behavior when an error occurs during the non blocking initialization
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestNonBlockingInitFail)
{
using namespace testing;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
EXPECT_CALL(ps4Impl, sceHttpsEnableOption(CONNECTION_ID, SCE_HTTPS_FLAG_SERVER_VERIFY)).WillOnce(Return(SCE_OK));
const SceInt32 REQUEST_ID = 6;
EXPECT_CALL(ps4Impl, sceHttpCreateRequestWithURL2(CONNECTION_ID, _, _, _)).WillOnce(Return(REQUEST_ID));
EXPECT_CALL(ps4Impl, sceHttpSetNonblock(REQUEST_ID, SCE_TRUE)).WillOnce(Return(SCE_OK));
EXPECT_CALL(ps4Impl, sceHttpCreateEpoll(_, _)).WillOnce(DoAll(
SetArgPointee<1>(reinterpret_cast<SceHttpEpollHandle>(1)), // arbitrary value to ensure eh will not be null
Return(SCE_OK)));
EXPECT_CALL(ps4Impl, sceHttpSetEpoll(REQUEST_ID, _, nullptr)).WillOnce(Return(SCE_HTTP_ERROR_INVALID_VALUE));
// Epoll couldn't be set, so it shall not be unset
// However the epoll and the request could be created, so they shall be released
EXPECT_CALL(ps4Impl, sceHttpUnsetEpoll(REQUEST_ID)).Times(0);
EXPECT_CALL(ps4Impl, sceHttpDestroyEpoll(_, _)).WillOnce(Return(SCE_OK));
EXPECT_CALL(ps4Impl, sceHttpDeleteRequest(REQUEST_ID)).WillOnce(Return(SCE_OK));
HttpRequestPs4 request(MockHttpRequest_BF::createDummyContext(), &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
EXPECT_FALSE(request.initialize(false));
}
/// @test Checks that the request is finished once the headers have been retrieved if there is no data to download
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestGETNoResponseBody)
{
using namespace testing;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
HttpRequestPs4 request(MockHttpRequest_BF::createDummyContext(), &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
const SceInt32 sceRequestId = 6;
MockHttpPs4Impl_BF::setRequestInitExpectations(ps4Impl, CONNECTION_ID, sceRequestId);
request.initialize(false);
EXPECT_CALL(ps4Impl, sceHttpSendRequest(sceRequestId, _, _)).WillOnce(Return(SCE_OK));
request.executeStep(); // stepSendRequest
EXPECT_CALL(ps4Impl, sceHttpGetResponseContentLength(sceRequestId, _, _)).WillOnce(DoAll(
SetArgPointee<1>(SCE_HTTP_CONTENTLEN_EXIST),
SetArgPointee<2>(0), Return(SCE_OK)));
request.executeStep(); // stepResponseContentLength
// response headers do not contain Content-Length or Content-Encoding => No respone body
const String responseHeaders = "HTTP/1.1 200 OK\r\n\r\n";
EXPECT_CALL(ps4Impl, sceHttpGetAllResponseHeaders(sceRequestId, _, _)).WillOnce(DoAll(
SetArgPointee<1>(const_cast<char*>(responseHeaders.getUtf8())),
SetArgPointee<2>(responseHeaders.getLength() + 1), Return(SCE_OK)));
EXPECT_CALL(ps4Impl, sceHttpGetStatusCode(sceRequestId, _)).WillOnce(Return(SCE_OK));
MockHttpPs4Impl_BF::setRequestReleaseExpectations(ps4Impl, sceRequestId);
request.executeStep(); // stepResponseHeaders
request.executeStep(); // stepParseHeaders
// The request should have been deleted in the previous step (since there are no data to download)
// Ensure here that the request is not deleted after stepResponseHeaders
MockHttpPs4Impl_BF::setRequestNoReleaseExpectations(ps4Impl);
}
/// @test Checks that the buffer size used to upload/download data
/// do not go over the maximum buffer size given to the request
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestBufferSize)
{
using namespace testing;
const String requestBody = "You wouldn't have won if we'd beaten you"; // Arbitrary length, but shall be above the maximum buffer size
HttpRequestContext requestContext = MockHttpRequest_BF::createDummyContext(HttpMethod::HTTP_POST, US_NEW(HttpStringEntity, requestBody), US_NEW(HttpBinaryEntity));
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
const uint32 MAX_BUFFER_SIZE = 10u; // Arbitrary value, but shall be below the request and response content lengths
HttpRequestPs4 request(requestContext, &ps4Impl, 3, CONNECTION_ID, MAX_BUFFER_SIZE); // Arbitrary values
const uint32 requestContentLength = requestBody.getLength();
const uint32 RESPONSE_CONTENT_LENGTH = 30u; // Arbitrary value, but shall be above the maximum buffer size
const SceInt32 REQUEST_ID = 6;
MockHttpPs4Impl_BF::setRequestInitExpectations(ps4Impl, CONNECTION_ID, REQUEST_ID);
request.initialize(false);
uint32 bytesLeft = requestContentLength;
ASSERT_GT(bytesLeft, MAX_BUFFER_SIZE);
EXPECT_CALL(ps4Impl, sceHttpSendRequest(REQUEST_ID, _, MAX_BUFFER_SIZE)).WillRepeatedly(Return(SCE_OK));
while (bytesLeft > MAX_BUFFER_SIZE)
{
request.executeStep(); // stepSendRequest
bytesLeft -= MAX_BUFFER_SIZE;
}
EXPECT_CALL(ps4Impl, sceHttpSendRequest(REQUEST_ID, _, bytesLeft)).WillRepeatedly(Return(SCE_OK));
request.executeStep(); // stepSendRequest
EXPECT_CALL(ps4Impl, sceHttpGetResponseContentLength(REQUEST_ID, _, _)).WillOnce(DoAll(
SetArgPointee<1>(SCE_HTTP_CONTENTLEN_EXIST),
SetArgPointee<2>(RESPONSE_CONTENT_LENGTH),
Return(SCE_OK)));
request.executeStep(); // stepResponseContentLength
StringStream responseHeadersStream;
responseHeadersStream << "HTTP/1.1 200 OK\r\nContent-Length: " << RESPONSE_CONTENT_LENGTH << "\r\n\r\n";
const String responseHeaders = responseHeadersStream.getContent();
EXPECT_CALL(ps4Impl, sceHttpGetAllResponseHeaders(REQUEST_ID, _, _)).WillOnce(DoAll(
SetArgPointee<1>(const_cast<char*>(responseHeaders.getUtf8())), SetArgPointee<2>(responseHeaders.getLength() + 1), Return(SCE_OK)));
EXPECT_CALL(ps4Impl, sceHttpGetStatusCode(REQUEST_ID, _)).WillOnce(Return(SCE_OK));
request.executeStep(); // stepResponseHeaders
request.executeStep(); // stepParseHeaders
bytesLeft = RESPONSE_CONTENT_LENGTH;
ASSERT_GT(bytesLeft, MAX_BUFFER_SIZE);
EXPECT_CALL(ps4Impl, sceHttpReadData(REQUEST_ID, _, MAX_BUFFER_SIZE)).WillRepeatedly(Return(MAX_BUFFER_SIZE));
while (bytesLeft > MAX_BUFFER_SIZE)
{
request.executeStep(); // stepDownloadData
bytesLeft -= MAX_BUFFER_SIZE;
}
EXPECT_CALL(ps4Impl, sceHttpReadData(REQUEST_ID, _, bytesLeft)).WillOnce(Return(bytesLeft));
MockHttpPs4Impl_BF::setRequestReleaseExpectations(ps4Impl, REQUEST_ID);
request.executeStep(); // stepDownloadData
// The request should have been deleted in the previous step (all data have been downloaded)
// Ensure here that the request is not deleted after the last call to stepDownloadData
MockHttpPs4Impl_BF::setRequestNoReleaseExpectations(ps4Impl);
}
/// @test Checks that resources are released when a request is canceled
TEST_F(HTTP_REQUEST_PS4_MOCK, LOCAL_RequestCancel)
{
using namespace testing;
MockHttpPs4Impl ps4Impl;
const uint32 CONNECTION_ID = 5u; // Arbitrary value
HttpRequestPs4 request(MockHttpRequest_BF::createDummyContext(), &ps4Impl, 3, CONNECTION_ID, 1024u); // Arbitrary values
const SceInt32 REQUEST_ID = 6;
EXPECT_CALL(ps4Impl, sceHttpAbortRequest(REQUEST_ID)).WillOnce(Return(0));
MockHttpPs4Impl_BF::setRequestInitExpectations(ps4Impl, CONNECTION_ID, REQUEST_ID);
request.initialize(false);
MockHttpPs4Impl_BF::setRequestReleaseExpectations(ps4Impl, REQUEST_ID);
request.cancelImpl();
// The request should have been deleted during the cancel
// Ensure here that the request is not deleted after the cancel
MockHttpPs4Impl_BF::setRequestNoReleaseExpectations(ps4Impl);
EXPECT_TRUE(request.isCanceled());
}
#endif //#if !defined(US_DLL)
};